diff options
20 files changed, 666 insertions, 4 deletions
diff --git a/cc/animation/animation_curve.h b/cc/animation/animation_curve.h index 3860234..f4cdb10 100644 --- a/cc/animation/animation_curve.h +++ b/cc/animation/animation_curve.h @@ -10,6 +10,10 @@ #include "cc/output/filter_operations.h" #include "ui/gfx/transform.h" +namespace gfx { +class BoxF; +} + namespace cc { class FilterAnimationCurve; @@ -49,6 +53,12 @@ class CC_EXPORT TransformAnimationCurve : public AnimationCurve { virtual gfx::Transform GetValue(double t) const = 0; + // Sets |bounds| to be the bounding box for the region within which |box| + // will move during this animation. If this region cannot be computed, + // returns false. + virtual bool AnimatedBoundsForBox(const gfx::BoxF& box, + gfx::BoxF* bounds) const = 0; + // Partial Animation implementation. virtual CurveType Type() const OVERRIDE; }; diff --git a/cc/animation/keyframed_animation_curve.cc b/cc/animation/keyframed_animation_curve.cc index c016c91..14dfd9c 100644 --- a/cc/animation/keyframed_animation_curve.cc +++ b/cc/animation/keyframed_animation_curve.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "cc/animation/keyframed_animation_curve.h" +#include "ui/gfx/box_f.h" namespace cc { @@ -231,6 +232,28 @@ gfx::Transform KeyframedTransformAnimationCurve::GetValue(double t) const { return GetCurveValue<gfx::Transform, TransformKeyframe>(&keyframes_, t); } +bool KeyframedTransformAnimationCurve::AnimatedBoundsForBox( + const gfx::BoxF& box, + gfx::BoxF* bounds) const { + DCHECK_GE(keyframes_.size(), 2ul); + *bounds = gfx::BoxF(); + for (size_t i = 0; i < keyframes_.size() - 1; ++i) { + gfx::BoxF bounds_for_step; + float min_progress = 0.0; + float max_progress = 1.0; + if (keyframes_[i]->timing_function()) + keyframes_[i]->timing_function()->Range(&min_progress, &max_progress); + if (!keyframes_[i+1]->Value().BlendedBoundsForBox(box, + keyframes_[i]->Value(), + min_progress, + max_progress, + &bounds_for_step)) + return false; + bounds->Union(bounds_for_step); + } + return true; +} + scoped_ptr<KeyframedFilterAnimationCurve> KeyframedFilterAnimationCurve:: Create() { return make_scoped_ptr(new KeyframedFilterAnimationCurve); diff --git a/cc/animation/keyframed_animation_curve.h b/cc/animation/keyframed_animation_curve.h index 9dda773..5892dc7 100644 --- a/cc/animation/keyframed_animation_curve.h +++ b/cc/animation/keyframed_animation_curve.h @@ -135,6 +135,8 @@ class CC_EXPORT KeyframedTransformAnimationCurve // TransformAnimationCurve implementation virtual gfx::Transform GetValue(double t) const OVERRIDE; + virtual bool AnimatedBoundsForBox(const gfx::BoxF& box, + gfx::BoxF* bounds) const OVERRIDE; private: KeyframedTransformAnimationCurve(); diff --git a/cc/animation/keyframed_animation_curve_unittest.cc b/cc/animation/keyframed_animation_curve_unittest.cc index 32efc14..48f511c0 100644 --- a/cc/animation/keyframed_animation_curve_unittest.cc +++ b/cc/animation/keyframed_animation_curve_unittest.cc @@ -7,6 +7,7 @@ #include "cc/animation/transform_operations.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/box_f.h" namespace cc { namespace { @@ -332,5 +333,29 @@ TEST(KeyframedAnimationCurveTest, CubicBezierTimingFunction) { EXPECT_FLOAT_EQ(1.f, curve->GetValue(1.f)); } +// Tests that animated bounds are computed as expected. +TEST(KeyframedAnimationCurveTest, AnimatedBounds) { + scoped_ptr<KeyframedTransformAnimationCurve> curve( + KeyframedTransformAnimationCurve::Create()); + + TransformOperations operations1; + curve->AddKeyframe(TransformKeyframe::Create( + 0.0, operations1, scoped_ptr<TimingFunction>())); + operations1.AppendTranslate(2.0, 3.0, -1.0); + curve->AddKeyframe(TransformKeyframe::Create( + 0.5, operations1, scoped_ptr<TimingFunction>())); + TransformOperations operations2; + operations2.AppendTranslate(4.0, 1.0, 2.0); + curve->AddKeyframe(TransformKeyframe::Create( + 1.0, operations2, EaseTimingFunction::Create())); + + gfx::BoxF box(2.f, 3.f, 4.f, 1.f, 3.f, 2.f); + gfx::BoxF bounds; + + EXPECT_TRUE(curve->AnimatedBoundsForBox(box, &bounds)); + EXPECT_EQ(gfx::BoxF(2.f, 3.f, 3.f, 5.f, 6.f, 5.f).ToString(), + bounds.ToString()); +} + } // namespace } // namespace cc diff --git a/cc/animation/layer_animation_controller.cc b/cc/animation/layer_animation_controller.cc index 5823b79..08e2db7 100644 --- a/cc/animation/layer_animation_controller.cc +++ b/cc/animation/layer_animation_controller.cc @@ -12,6 +12,7 @@ #include "cc/animation/keyframed_animation_curve.h" #include "cc/animation/layer_animation_value_observer.h" #include "cc/base/scoped_ptr_algorithm.h" +#include "ui/gfx/box_f.h" #include "ui/gfx/transform.h" namespace cc { @@ -343,6 +344,31 @@ void LayerAnimationController::RemoveEventObserver( event_observers_.RemoveObserver(observer); } +bool LayerAnimationController::AnimatedBoundsForBox(const gfx::BoxF& box, + gfx::BoxF* bounds) { + // Compute bounds based on animations for which is_finished() is false. + // Do nothing if there are no such animations; in this case, it is assumed + // that callers will take care of computing bounds based on the owning layer's + // actual transform. + *bounds = gfx::BoxF(); + for (size_t i = 0; i < active_animations_.size(); ++i) { + if (active_animations_[i]->is_finished() || + active_animations_[i]->target_property() != Animation::Transform) + continue; + + const TransformAnimationCurve* transform_animation_curve = + active_animations_[i]->curve()->ToTransformAnimationCurve(); + gfx::BoxF animation_bounds; + bool success = + transform_animation_curve->AnimatedBoundsForBox(box, &animation_bounds); + if (!success) + return false; + bounds->Union(animation_bounds); + } + + return true; +} + void LayerAnimationController::PushNewAnimationsToImplThread( LayerAnimationController* controller_impl) const { // Any new animations owned by the main thread's controller are cloned and diff --git a/cc/animation/layer_animation_controller.h b/cc/animation/layer_animation_controller.h index e450f15..4138704 100644 --- a/cc/animation/layer_animation_controller.h +++ b/cc/animation/layer_animation_controller.h @@ -17,7 +17,10 @@ #include "cc/base/scoped_ptr_vector.h" #include "ui/gfx/transform.h" -namespace gfx { class Transform; } +namespace gfx { +class BoxF; +class Transform; +} namespace cc { @@ -100,6 +103,8 @@ class CC_EXPORT LayerAnimationController layer_animation_delegate_ = delegate; } + bool AnimatedBoundsForBox(const gfx::BoxF& box, gfx::BoxF* bounds); + protected: friend class base::RefCounted<LayerAnimationController>; diff --git a/cc/animation/layer_animation_controller_unittest.cc b/cc/animation/layer_animation_controller_unittest.cc index 2e06f5c..6e94734 100644 --- a/cc/animation/layer_animation_controller_unittest.cc +++ b/cc/animation/layer_animation_controller_unittest.cc @@ -12,6 +12,7 @@ #include "cc/test/animation_test_common.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/box_f.h" #include "ui/gfx/transform.h" namespace cc { @@ -1117,5 +1118,74 @@ TEST(LayerAnimationControllerTest, InactiveObserverGetsTicked) { EXPECT_NE(0.5f, dummy.opacity()); } +TEST(LayerAnimationControllerTest, AnimatedBounds) { + scoped_refptr<LayerAnimationController> controller_impl( + LayerAnimationController::Create(0)); + + scoped_ptr<KeyframedTransformAnimationCurve> curve1( + KeyframedTransformAnimationCurve::Create()); + + TransformOperations operations1; + curve1->AddKeyframe(TransformKeyframe::Create( + 0.0, operations1, scoped_ptr<TimingFunction>())); + operations1.AppendTranslate(10.0, 15.0, 0.0); + curve1->AddKeyframe(TransformKeyframe::Create( + 1.0, operations1, scoped_ptr<TimingFunction>())); + + scoped_ptr<Animation> animation(Animation::Create( + curve1.PassAs<AnimationCurve>(), 1, 1, Animation::Transform)); + controller_impl->AddAnimation(animation.Pass()); + + scoped_ptr<KeyframedTransformAnimationCurve> curve2( + KeyframedTransformAnimationCurve::Create()); + + TransformOperations operations2; + curve2->AddKeyframe(TransformKeyframe::Create( + 0.0, operations2, scoped_ptr<TimingFunction>())); + operations2.AppendScale(2.0, 3.0, 4.0); + curve2->AddKeyframe(TransformKeyframe::Create( + 1.0, operations2, scoped_ptr<TimingFunction>())); + + animation = Animation::Create( + curve2.PassAs<AnimationCurve>(), 2, 2, Animation::Transform); + controller_impl->AddAnimation(animation.Pass()); + + gfx::BoxF box(1.f, 2.f, -1.f, 3.f, 4.f, 5.f); + gfx::BoxF bounds; + + EXPECT_TRUE(controller_impl->AnimatedBoundsForBox(box, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, 2.f, -4.f, 13.f, 19.f, 20.f).ToString(), + bounds.ToString()); + + controller_impl->GetAnimation(1, Animation::Transform)->SetRunState( + cc::Animation::Finished, 0.0); + + // Only the unfinished animation should affect the animated bounds. + EXPECT_TRUE(controller_impl->AnimatedBoundsForBox(box, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, 2.f, -4.f, 7.f, 16.f, 20.f).ToString(), + bounds.ToString()); + + controller_impl->GetAnimation(2, Animation::Transform)->SetRunState( + cc::Animation::Finished, 0.0); + + // There are no longer any running animations. + EXPECT_TRUE(controller_impl->AnimatedBoundsForBox(box, &bounds)); + EXPECT_EQ(gfx::BoxF().ToString(), bounds.ToString()); + + // Add an animation whose bounds we don't yet support computing. + scoped_ptr<KeyframedTransformAnimationCurve> curve3( + KeyframedTransformAnimationCurve::Create()); + TransformOperations operations3; + curve3->AddKeyframe(TransformKeyframe::Create( + 0.0, operations3, scoped_ptr<TimingFunction>())); + operations3.AppendSkew(1.0, 2.0); + curve3->AddKeyframe(TransformKeyframe::Create( + 1.0, operations3, scoped_ptr<TimingFunction>())); + animation = Animation::Create( + curve3.PassAs<AnimationCurve>(), 3, 3, Animation::Transform); + controller_impl->AddAnimation(animation.Pass()); + EXPECT_FALSE(controller_impl->AnimatedBoundsForBox(box, &bounds)); +} + } // namespace } // namespace cc diff --git a/cc/animation/timing_function.cc b/cc/animation/timing_function.cc index 65d607f..7fdb37f 100644 --- a/cc/animation/timing_function.cc +++ b/cc/animation/timing_function.cc @@ -12,7 +12,7 @@ namespace cc { namespace { -static const double BEZIER_EPSILON = 1e-7; +static const double kBezierEpsilon = 1e-7; static const int MAX_STEPS = 30; static double eval_bezier(double x1, double x2, double t) { @@ -48,14 +48,14 @@ static double bezier_interp(double x1, double step = 1.0; for (int i = 0; i < MAX_STEPS; ++i, step *= 0.5) { const double error = eval_bezier(x1, x2, t) - x; - if (std::abs(error) < BEZIER_EPSILON) + if (std::abs(error) < kBezierEpsilon) break; t += error > 0.0 ? -step : step; } // We should have terminated the above loop because we got close to x, not // because we exceeded MAX_STEPS. Do a DCHECK here to confirm. - DCHECK_GT(BEZIER_EPSILON, std::abs(eval_bezier(x1, x2, t) - x)); + DCHECK_GT(kBezierEpsilon, std::abs(eval_bezier(x1, x2, t) - x)); // Step 2. Return the interpolated y values at the t we computed above. return eval_bezier(y1, y2, t); @@ -93,6 +93,53 @@ scoped_ptr<AnimationCurve> CubicBezierTimingFunction::Clone() const { new CubicBezierTimingFunction(*this)).PassAs<AnimationCurve>(); } +void CubicBezierTimingFunction::Range(float* min, float* max) const { + *min = 0.f; + *max = 1.f; + if (0.f <= y1_ && y1_ < 1.f && 0.f <= y2_ && y2_ <= 1.f) + return; + + // Represent the function's derivative in the form at^2 + bt + c. + float a = 3.f * (y1_ - y2_) + 1.f; + float b = 2.f * (y2_ - 2.f * y1_); + float c = y1_; + + // Check if the derivative is constant. + if (std::abs(a) < kBezierEpsilon && + std::abs(b) < kBezierEpsilon) + return; + + // Zeros of the function's derivative. + float t_1 = 0.f; + float t_2 = 0.f; + + if (std::abs(a) < kBezierEpsilon) { + // The function's derivative is linear. + t_1 = -c / b; + } else { + // The function's derivative is a quadratic. We find the zeros of this + // quadratic using the quadratic formula. + float discriminant = b * b - 4 * a * c; + if (discriminant < 0.f) + return; + float discriminant_sqrt = sqrt(discriminant); + t_1 = (-b + discriminant_sqrt) / (2.f * a); + t_2 = (-b - discriminant_sqrt) / (2.f * a); + } + + float sol_1 = 0.f; + float sol_2 = 0.f; + + if (0.f < t_1 && t_1 < 1.f) + sol_1 = eval_bezier(y1_, y2_, t_1); + + if (0.f < t_2 && t_2 < 1.f) + sol_2 = eval_bezier(y1_, y2_, t_2); + + *min = std::min(std::min(*min, sol_1), sol_2); + *max = std::max(std::max(*max, sol_1), sol_2); +} + // These numbers come from // http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag. scoped_ptr<TimingFunction> EaseTimingFunction::Create() { diff --git a/cc/animation/timing_function.h b/cc/animation/timing_function.h index 3aa2f25..056ad41 100644 --- a/cc/animation/timing_function.h +++ b/cc/animation/timing_function.h @@ -19,6 +19,10 @@ class CC_EXPORT TimingFunction : public FloatAnimationCurve { // Partial implementation of FloatAnimationCurve. virtual double Duration() const OVERRIDE; + // The smallest and largest values returned by GetValue for inputs in + // [0, 1]. + virtual void Range(float* min, float* max) const = 0; + protected: TimingFunction(); @@ -36,6 +40,8 @@ class CC_EXPORT CubicBezierTimingFunction : public TimingFunction { virtual float GetValue(double time) const OVERRIDE; virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE; + virtual void Range(float* min, float* max) const OVERRIDE; + protected: CubicBezierTimingFunction(double x1, double y1, double x2, double y2); diff --git a/cc/animation/timing_function_unittest.cc b/cc/animation/timing_function_unittest.cc index 2caa12d..4be225f 100644 --- a/cc/animation/timing_function_unittest.cc +++ b/cc/animation/timing_function_unittest.cc @@ -67,5 +67,75 @@ TEST(TimingFunctionTest, CubicBezierTimingFunctionUnclampedYValues) { EXPECT_NEAR(function->GetValue(1.0), 1.0, epsilon); } +TEST(TimingFunctionTest, CubicBezierTimingFunctionRange) { + double epsilon = 0.00015; + float min, max; + + // Derivative is a constant. + scoped_ptr<CubicBezierTimingFunction> function = + CubicBezierTimingFunction::Create(0.25, (1.0 / 3.0), 0.75, (2.0 / 3.0)); + function->Range(&min, &max); + EXPECT_EQ(0.f, min); + EXPECT_EQ(1.f, max); + + // Derivative is linear. + function = CubicBezierTimingFunction::Create(0.25, -0.5, 0.75, (-1.0 / 6.0)); + function->Range(&min, &max); + EXPECT_NEAR(min, -0.225, epsilon); + EXPECT_EQ(1.f, max); + + // Derivative has no real roots. + function = CubicBezierTimingFunction::Create(0.25, 0.25, 0.75, 0.5); + function->Range(&min, &max); + EXPECT_EQ(0.f, min); + EXPECT_EQ(1.f, max); + + // Derivative has exactly one real root. + function = CubicBezierTimingFunction::Create(0.0, 1.0, 1.0, 0.0); + function->Range(&min, &max); + EXPECT_EQ(0.f, min); + EXPECT_EQ(1.f, max); + + // Derivative has one root < 0 and one root > 1. + function = CubicBezierTimingFunction::Create(0.25, 0.1, 0.75, 0.9); + function->Range(&min, &max); + EXPECT_EQ(0.f, min); + EXPECT_EQ(1.f, max); + + // Derivative has two roots in [0,1]. + function = CubicBezierTimingFunction::Create(0.25, 2.5, 0.75, 0.5); + function->Range(&min, &max); + EXPECT_EQ(0.f, min); + EXPECT_NEAR(max, 1.28818, epsilon); + function = CubicBezierTimingFunction::Create(0.25, 0.5, 0.75, -1.5); + function->Range(&min, &max); + EXPECT_NEAR(min, -0.28818, epsilon); + EXPECT_EQ(1.f, max); + + // Derivative has one root < 0 and one root in [0,1]. + function = CubicBezierTimingFunction::Create(0.25, 0.1, 0.75, 1.5); + function->Range(&min, &max); + EXPECT_EQ(0.f, min); + EXPECT_NEAR(max, 1.10755, epsilon); + + // Derivative has one root in [0,1] and one root > 1. + function = CubicBezierTimingFunction::Create(0.25, -0.5, 0.75, 0.9); + function->Range(&min, &max); + EXPECT_NEAR(min, -0.10755, epsilon); + EXPECT_EQ(1.f, max); + + // Derivative has two roots < 0. + function = CubicBezierTimingFunction::Create(0.25, 0.3, 0.75, 0.633); + function->Range(&min, &max); + EXPECT_EQ(0.f, min); + EXPECT_EQ(1.f, max); + + // Derivative has two roots > 1. + function = CubicBezierTimingFunction::Create(0.25, 0.367, 0.75, 0.7); + function->Range(&min, &max); + EXPECT_EQ(0.f, min); + EXPECT_EQ(1.f, max); +} + } // namespace } // namespace cc diff --git a/cc/animation/transform_operation.cc b/cc/animation/transform_operation.cc index dacea06..b7bb063 100644 --- a/cc/animation/transform_operation.cc +++ b/cc/animation/transform_operation.cc @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <algorithm> #include <cmath> #include <limits> #include "cc/animation/transform_operation.h" +#include "ui/gfx/box_f.h" #include "ui/gfx/vector3d_f.h" namespace { @@ -177,4 +179,123 @@ bool TransformOperation::BlendTransformOperations( return true; } +static void ApplyScaleToBox(float x_scale, + float y_scale, + float z_scale, + gfx::BoxF* box) { + if (x_scale < 0) + box->set_x(-box->right()); + if (y_scale < 0) + box->set_y(-box->bottom()); + if (z_scale < 0) + box->set_z(-box->front()); + box->Scale(std::abs(x_scale), std::abs(y_scale), std::abs(z_scale)); +} + +static void UnionBoxWithZeroScale(gfx::BoxF* box) { + float min_x = std::min(box->x(), 0.f); + float min_y = std::min(box->y(), 0.f); + float min_z = std::min(box->z(), 0.f); + float max_x = std::max(box->right(), 0.f); + float max_y = std::max(box->bottom(), 0.f); + float max_z = std::max(box->front(), 0.f); + *box = gfx::BoxF( + min_x, min_y, min_z, max_x - min_x, max_y - min_y, max_z - min_z); +} + +bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box, + const TransformOperation* from, + const TransformOperation* to, + double min_progress, + double max_progress, + gfx::BoxF* bounds) { + bool is_identity_from = IsOperationIdentity(from); + bool is_identity_to = IsOperationIdentity(to); + if (is_identity_from && is_identity_to) { + *bounds = box; + return true; + } + + TransformOperation::Type interpolation_type = + TransformOperation::TransformOperationIdentity; + if (is_identity_to) + interpolation_type = from->type; + else + interpolation_type = to->type; + + switch (interpolation_type) { + case TransformOperation::TransformOperationTranslate: { + double from_x, from_y, from_z; + if (is_identity_from) { + from_x = from_y = from_z = 0.0; + } else { + from_x = from->translate.x; + from_y = from->translate.y; + from_z = from->translate.z; + } + double to_x, to_y, to_z; + if (is_identity_to) { + to_x = to_y = to_z = 0.0; + } else { + to_x = to->translate.x; + to_y = to->translate.y; + to_z = to->translate.z; + } + *bounds = box; + *bounds += gfx::Vector3dF(BlendDoubles(from_x, to_x, min_progress), + BlendDoubles(from_y, to_y, min_progress), + BlendDoubles(from_z, to_z, min_progress)); + gfx::BoxF bounds_max = box; + bounds_max += gfx::Vector3dF(BlendDoubles(from_x, to_x, max_progress), + BlendDoubles(from_y, to_y, max_progress), + BlendDoubles(from_z, to_z, max_progress)); + bounds->Union(bounds_max); + return true; + } + case TransformOperation::TransformOperationScale: { + double from_x, from_y, from_z; + if (is_identity_from) { + from_x = from_y = from_z = 1.0; + } else { + from_x = from->scale.x; + from_y = from->scale.y; + from_z = from->scale.z; + } + double to_x, to_y, to_z; + if (is_identity_to) { + to_x = to_y = to_z = 1.0; + } else { + to_x = to->scale.x; + to_y = to->scale.y; + to_z = to->scale.z; + } + *bounds = box; + ApplyScaleToBox(BlendDoubles(from_x, to_x, min_progress), + BlendDoubles(from_y, to_y, min_progress), + BlendDoubles(from_z, to_z, min_progress), + bounds); + gfx::BoxF bounds_max = box; + ApplyScaleToBox(BlendDoubles(from_x, to_x, max_progress), + BlendDoubles(from_y, to_y, max_progress), + BlendDoubles(from_z, to_z, max_progress), + &bounds_max); + if (!bounds->IsEmpty() && !bounds_max.IsEmpty()) { + bounds->Union(bounds_max); + } else if (!bounds->IsEmpty()) { + UnionBoxWithZeroScale(bounds); + } else if (!bounds_max.IsEmpty()) { + UnionBoxWithZeroScale(&bounds_max); + *bounds = bounds_max; + } + + return true; + } + case TransformOperation::TransformOperationIdentity: + *bounds = box; + return true; + default: + return false; + } +} + } // namespace cc diff --git a/cc/animation/transform_operation.h b/cc/animation/transform_operation.h index 74673ab..d5f2830 100644 --- a/cc/animation/transform_operation.h +++ b/cc/animation/transform_operation.h @@ -7,6 +7,10 @@ #include "ui/gfx/transform.h" +namespace gfx { +class BoxF; +} + namespace cc { struct TransformOperation { @@ -56,6 +60,13 @@ struct TransformOperation { const TransformOperation* to, double progress, gfx::Transform* result); + + static bool BlendedBoundsForBox(const gfx::BoxF& box, + const TransformOperation* from, + const TransformOperation* to, + double min_progress, + double max_progress, + gfx::BoxF* bounds); }; } // namespace cc diff --git a/cc/animation/transform_operations.cc b/cc/animation/transform_operations.cc index d74f1a5..42b3559 100644 --- a/cc/animation/transform_operations.cc +++ b/cc/animation/transform_operations.cc @@ -6,6 +6,7 @@ #include <algorithm> +#include "ui/gfx/box_f.h" #include "ui/gfx/transform_util.h" #include "ui/gfx/vector3d_f.h" @@ -41,6 +42,42 @@ gfx::Transform TransformOperations::Blend( return to_return; } +bool TransformOperations::BlendedBoundsForBox(const gfx::BoxF& box, + const TransformOperations& from, + double min_progress, + double max_progress, + gfx::BoxF* bounds) const { + *bounds = box; + + bool from_identity = from.IsIdentity(); + bool to_identity = IsIdentity(); + if (from_identity && to_identity) + return true; + + if (!MatchesTypes(from)) + return false; + + size_t num_operations = + std::max(from_identity ? 0 : from.operations_.size(), + to_identity ? 0 : operations_.size()); + for (size_t i = 0; i < num_operations; ++i) { + gfx::BoxF bounds_for_operation; + const TransformOperation* from_op = + from_identity ? NULL : &from.operations_[i]; + const TransformOperation* to_op = to_identity ? NULL : &operations_[i]; + if (!TransformOperation::BlendedBoundsForBox(*bounds, + from_op, + to_op, + min_progress, + max_progress, + &bounds_for_operation)) + return false; + *bounds = bounds_for_operation; + } + + return true; +} + bool TransformOperations::MatchesTypes(const TransformOperations& other) const { if (IsIdentity() || other.IsIdentity()) return true; diff --git a/cc/animation/transform_operations.h b/cc/animation/transform_operations.h index b5f960f..96bf1d1 100644 --- a/cc/animation/transform_operations.h +++ b/cc/animation/transform_operations.h @@ -13,6 +13,7 @@ #include "ui/gfx/transform.h" namespace gfx { +class BoxF; struct DecomposedTransform; } @@ -45,6 +46,16 @@ class CC_EXPORT TransformOperations { // http://www.w3.org/TR/2011/WD-css3-2d-transforms-20111215/#matrix-decomposition. gfx::Transform Blend(const TransformOperations& from, double progress) const; + // Sets |bounds| be the bounding box for the region within which |box| will + // exist when it is transformed by the result of calling Blend on |from| and + // with progress in the range [min_progress, max_progress]. If this region + // cannot be computed, returns false. + bool BlendedBoundsForBox(const gfx::BoxF& box, + const TransformOperations& from, + double min_progress, + double max_progress, + gfx::BoxF* bounds) const; + // Returns true if this operation and its descendants have the same types // as other and its descendants. bool MatchesTypes(const TransformOperations& other) const; diff --git a/cc/animation/transform_operations_unittest.cc b/cc/animation/transform_operations_unittest.cc index 8fc9b94..b82d911 100644 --- a/cc/animation/transform_operations_unittest.cc +++ b/cc/animation/transform_operations_unittest.cc @@ -8,6 +8,7 @@ #include "cc/animation/transform_operations.h" #include "cc/test/geometry_test_utils.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/box_f.h" #include "ui/gfx/vector3d_f.h" namespace cc { @@ -707,5 +708,176 @@ TEST(TransformOperationTest, ExtrapolateMatrixBlending) { expected, operations1.Blend(operations2, -0.5)); } +TEST(TransformOperationTest, BlendedBoundsWhenTypesDoNotMatch) { + TransformOperations operations_from; + operations_from.AppendScale(2.0, 4.0, 8.0); + operations_from.AppendTranslate(1.0, 2.0, 3.0); + + TransformOperations operations_to; + operations_to.AppendTranslate(10.0, 20.0, 30.0); + operations_to.AppendScale(4.0, 8.0, 16.0); + + gfx::BoxF box(1.f, 1.f, 1.f); + gfx::BoxF bounds; + + double min_progress = 0.0; + double max_progress = 1.0; + + EXPECT_FALSE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); +} + +TEST(TransformOperationTest, BlendedBoundsForIdentity) { + TransformOperations operations_from; + operations_from.AppendIdentity(); + TransformOperations operations_to; + operations_to.AppendIdentity(); + + gfx::BoxF box(1.f, 2.f, 3.f); + gfx::BoxF bounds; + + double min_progress = 0.0; + double max_progress = 1.0; + + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(box.ToString(), bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForTranslate) { + TransformOperations operations_from; + operations_from.AppendTranslate(3.0, -4.0, 2.0); + TransformOperations operations_to; + operations_to.AppendTranslate(7.0, 4.0, -2.0); + + gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f); + gfx::BoxF bounds; + + double min_progress = -0.5; + double max_progress = 1.5; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(2.f, -6.f, -1.f, 12.f, 20.f, 12.f).ToString(), + bounds.ToString()); + + min_progress = 0.0; + max_progress = 1.0; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(4.f, -2.f, 1.f, 8.f, 12.f, 8.f).ToString(), + bounds.ToString()); + + TransformOperations identity; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, identity, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, 2.f, 1.f, 11.f, 8.f, 6.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(identity.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, -2.f, 3.f, 7.f, 8.f, 6.f).ToString(), + bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForScale) { + TransformOperations operations_from; + operations_from.AppendScale(3.0, 0.5, 2.0); + TransformOperations operations_to; + operations_to.AppendScale(7.0, 4.0, -2.0); + + gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f); + gfx::BoxF bounds; + + double min_progress = -0.5; + double max_progress = 1.5; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, -7.5f, -28.f, 44.f, 42.f, 56.f).ToString(), + bounds.ToString()); + + min_progress = 0.0; + max_progress = 1.0; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(3.f, 1.f, -14.f, 32.f, 23.f, 28.f).ToString(), + bounds.ToString()); + + TransformOperations identity; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, identity, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, 2.f, -14.f, 34.f, 22.f, 21.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(identity.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, 1.f, 3.f, 14.f, 5.f, 11.f).ToString(), + bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsWithZeroScale) { + TransformOperations zero_scale; + zero_scale.AppendScale(0.0, 0.0, 0.0); + TransformOperations non_zero_scale; + non_zero_scale.AppendScale(2.0, -4.0, 5.0); + + gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f); + gfx::BoxF bounds; + + double min_progress = 0.0; + double max_progress = 1.0; + EXPECT_TRUE(zero_scale.BlendedBoundsForBox( + box, non_zero_scale, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(0.f, -24.f, 0.f, 10.f, 24.f, 35.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(non_zero_scale.BlendedBoundsForBox( + box, zero_scale, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(0.f, -24.f, 0.f, 10.f, 24.f, 35.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(zero_scale.BlendedBoundsForBox( + box, zero_scale, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF().ToString(), bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForSequence) { + TransformOperations operations_from; + operations_from.AppendTranslate(2.0, 4.0, -1.0); + operations_from.AppendScale(-1.0, 2.0, 3.0); + operations_from.AppendTranslate(1.0, -5.0, 1.0); + TransformOperations operations_to; + operations_to.AppendTranslate(6.0, -2.0, 3.0); + operations_to.AppendScale(-3.0, -2.0, 5.0); + operations_to.AppendTranslate(13.0, -1.0, 5.0); + + gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f); + gfx::BoxF bounds; + + double min_progress = -0.5; + double max_progress = 1.5; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-57.f, -59.f, -1.f, 76.f, 112.f, 80.f).ToString(), + bounds.ToString()); + + min_progress = 0.0; + max_progress = 1.0; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-32.f, -25.f, 7.f, 42.f, 44.f, 48.f).ToString(), + bounds.ToString()); + + TransformOperations identity; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, identity, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-33.f, -13.f, 3.f, 57.f, 19.f, 52.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(identity.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-7.f, -3.f, 2.f, 15.f, 23.f, 20.f).ToString(), + bounds.ToString()); +} + } // namespace } // namespace cc diff --git a/cc/layers/layer.h b/cc/layers/layer.h index 035e071..6c08071 100644 --- a/cc/layers/layer.h +++ b/cc/layers/layer.h @@ -30,6 +30,10 @@ #include "ui/gfx/rect_f.h" #include "ui/gfx/transform.h" +namespace gfx { +class BoxF; +} + namespace cc { class Animation; @@ -328,6 +332,10 @@ class CC_EXPORT Layer : public base::RefCounted<Layer>, void SuspendAnimations(double monotonic_time); void ResumeAnimations(double monotonic_time); + bool AnimatedBoundsForBox(const gfx::BoxF& box, gfx::BoxF* bounds) { + return layer_animation_controller_->AnimatedBoundsForBox(box, bounds); + } + LayerAnimationController* layer_animation_controller() { return layer_animation_controller_.get(); } diff --git a/cc/test/animation_test_common.cc b/cc/test/animation_test_common.cc index a667c1b..cfab637 100644 --- a/cc/test/animation_test_common.cc +++ b/cc/test/animation_test_common.cc @@ -124,6 +124,11 @@ gfx::Transform FakeTransformTransition::GetValue(double time) const { return gfx::Transform(); } +bool FakeTransformTransition::AnimatedBoundsForBox(const gfx::BoxF& box, + gfx::BoxF* bounds) const { + return false; +} + scoped_ptr<cc::AnimationCurve> FakeTransformTransition::Clone() const { return make_scoped_ptr( new FakeTransformTransition(*this)).PassAs<cc::AnimationCurve>(); diff --git a/cc/test/animation_test_common.h b/cc/test/animation_test_common.h index cab2ccf..4d3efa6 100644 --- a/cc/test/animation_test_common.h +++ b/cc/test/animation_test_common.h @@ -38,6 +38,8 @@ class FakeTransformTransition : public TransformAnimationCurve { virtual double Duration() const OVERRIDE; virtual gfx::Transform GetValue(double time) const OVERRIDE; + virtual bool AnimatedBoundsForBox(const gfx::BoxF& box, + gfx::BoxF* bounds) const OVERRIDE; virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE; diff --git a/ui/compositor/transform_animation_curve_adapter.cc b/ui/compositor/transform_animation_curve_adapter.cc index 96500b3..4b493bc 100644 --- a/ui/compositor/transform_animation_curve_adapter.cc +++ b/ui/compositor/transform_animation_curve_adapter.cc @@ -51,4 +51,13 @@ gfx::Transform TransformAnimationCurveAdapter::GetValue( return gfx::ComposeTransform(to_return); } +bool TransformAnimationCurveAdapter::AnimatedBoundsForBox( + const gfx::BoxF& box, + gfx::BoxF* bounds) const { + // TODO(ajuma): Once cc::TransformOperation::BlendedBoundsForBox supports + // computing bounds for TransformOperationMatrix, use that to compute + // the bounds we need here. + return false; +} + } // namespace ui diff --git a/ui/compositor/transform_animation_curve_adapter.h b/ui/compositor/transform_animation_curve_adapter.h index 7b6bdce..e721550 100644 --- a/ui/compositor/transform_animation_curve_adapter.h +++ b/ui/compositor/transform_animation_curve_adapter.h @@ -26,6 +26,8 @@ class TransformAnimationCurveAdapter :public cc::TransformAnimationCurve { virtual double Duration() const OVERRIDE; virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE; virtual gfx::Transform GetValue(double t) const OVERRIDE; + virtual bool AnimatedBoundsForBox(const gfx::BoxF& box, + gfx::BoxF* bounds) const OVERRIDE; private: Tween::Type tween_type_; |