diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-30 17:02:21 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-30 17:02:21 +0000 |
commit | ad0f78a229deb9e26d7a2826723c9b5874a59108 (patch) | |
tree | 834986ea98cd81ca2b63ac28d4eea3816a9deee6 | |
parent | 627f2154be5f99126b864d0d93efb045f0bebc44 (diff) | |
download | chromium_src-ad0f78a229deb9e26d7a2826723c9b5874a59108.zip chromium_src-ad0f78a229deb9e26d7a2826723c9b5874a59108.tar.gz chromium_src-ad0f78a229deb9e26d7a2826723c9b5874a59108.tar.bz2 |
Lands http://codereview.chromium.org/7262024/ for Ian:
Added interpolated transforms
BUG= none
TEST=ui_unittests
Review URL: http://codereview.chromium.org/7285003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@91145 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ui/gfx/interpolated_transform.cc | 219 | ||||
-rw-r--r-- | ui/gfx/interpolated_transform.h | 195 | ||||
-rw-r--r-- | ui/gfx/interpolated_transform_unittest.cc | 112 | ||||
-rw-r--r-- | ui/ui_gfx.gypi | 2 | ||||
-rw-r--r-- | ui/ui_unittests.gypi | 1 |
5 files changed, 529 insertions, 0 deletions
diff --git a/ui/gfx/interpolated_transform.cc b/ui/gfx/interpolated_transform.cc new file mode 100644 index 0000000..1f37f84 --- /dev/null +++ b/ui/gfx/interpolated_transform.cc @@ -0,0 +1,219 @@ +// Copyright (c) 2011 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/interpolated_transform.h" + +#include "base/logging.h" +#include "ui/base/animation/tween.h" + +namespace ui { + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedTransform +// + +InterpolatedTransform::InterpolatedTransform() + : start_time_(0.0f), + end_time_(1.0f) { +} + +InterpolatedTransform::InterpolatedTransform(float start_time, + float end_time) + : start_time_(start_time), + end_time_(end_time) { +} + +InterpolatedTransform::~InterpolatedTransform() {} + +ui::Transform InterpolatedTransform::Interpolate(float t) const { + ui::Transform result = InterpolateButDoNotCompose(t); + if (child_.get()) { + result.ConcatTransform(child_->Interpolate(t)); + } + return result; +} + +void InterpolatedTransform::SetChild(InterpolatedTransform* child) { + child_.reset(child); +} + +inline float InterpolatedTransform::ValueBetween(float time, + float start_value, + float end_value) const { + // can't handle NaN + DCHECK(time == time && start_time_ == start_time_ && end_time_ == end_time_); + if (time != time || start_time_ != start_time_ || end_time_ != end_time_) + return start_value; + + // Ok if equal -- we'll get a step function. Note: if end_time_ == + // start_time_ == x, then if none of the numbers are NaN, then it + // must be true that time < x or time >= x, so we will return early + // due to one of the following if statements. + DCHECK(end_time_ >= start_time_); + + if (time < start_time_) + return start_value; + + if (time >= end_time_) + return end_value; + + float t = (time - start_time_) / (end_time_ - start_time_); + return static_cast<float>(Tween::ValueBetween(t, start_value, end_value)); +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedRotation +// + +InterpolatedRotation::InterpolatedRotation(float start_degrees, + float end_degrees) + : InterpolatedTransform(), + start_degrees_(start_degrees), + end_degrees_(end_degrees) { +} + +InterpolatedRotation::InterpolatedRotation(float start_degrees, + float end_degrees, + float start_time, + float end_time) + : InterpolatedTransform(start_time, end_time), + start_degrees_(start_degrees), + end_degrees_(end_degrees) { +} + +InterpolatedRotation::~InterpolatedRotation() {} + +ui::Transform InterpolatedRotation::InterpolateButDoNotCompose(float t) const { + ui::Transform result; + result.SetRotate(ValueBetween(t, start_degrees_, end_degrees_)); + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedScale +// + +InterpolatedScale::InterpolatedScale(float start_scale, + float end_scale) + : InterpolatedTransform(), + start_scale_(start_scale), + end_scale_(end_scale) { +} + +InterpolatedScale::InterpolatedScale(float start_scale, + float end_scale, + float start_time, + float end_time) + : InterpolatedTransform(start_time, end_time), + start_scale_(start_scale), + end_scale_(end_scale) { +} + +InterpolatedScale::~InterpolatedScale() {} + +ui::Transform InterpolatedScale::InterpolateButDoNotCompose(float t) const { + ui::Transform result; + float interpolated_scale = ValueBetween(t, start_scale_, end_scale_); + // TODO(vollick) 3d xforms. + result.SetScale(interpolated_scale, interpolated_scale); + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedTranslation +// + +InterpolatedTranslation::InterpolatedTranslation(const gfx::Point& start_pos, + const gfx::Point& end_pos) + : InterpolatedTransform(), + start_pos_(start_pos), + end_pos_(end_pos) { +} + +InterpolatedTranslation::InterpolatedTranslation(const gfx::Point& start_pos, + const gfx::Point& end_pos, + float start_time, + float end_time) + : InterpolatedTransform(start_time, end_time), + start_pos_(start_pos), + end_pos_(end_pos) { +} + +InterpolatedTranslation::~InterpolatedTranslation() {} + +ui::Transform +InterpolatedTranslation::InterpolateButDoNotCompose(float t) const { + ui::Transform result; + // TODO(vollick) 3d xforms. + result.SetTranslate(ValueBetween(t, start_pos_.x(), end_pos_.x()), + ValueBetween(t, start_pos_.y(), end_pos_.y())); + + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedConstantTransform +// + +InterpolatedConstantTransform::InterpolatedConstantTransform( + const ui::Transform& transform) + : InterpolatedTransform(), + transform_(transform) { +} + +ui::Transform +InterpolatedConstantTransform::InterpolateButDoNotCompose(float t) const { + return transform_; +} + +InterpolatedConstantTransform::~InterpolatedConstantTransform() {} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedTransformAboutPivot +// + +InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot( + const gfx::Point& pivot, + InterpolatedTransform* transform) + : InterpolatedTransform() { + Init(pivot, transform); +} + +InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot( + const gfx::Point& pivot, + InterpolatedTransform* transform, + float start_time, + float end_time) + : InterpolatedTransform() { + Init(pivot, transform); +} + +InterpolatedTransformAboutPivot::~InterpolatedTransformAboutPivot() {} + +ui::Transform +InterpolatedTransformAboutPivot::InterpolateButDoNotCompose(float t) const { + if (transform_.get()) { + return transform_->Interpolate(t); + } + return ui::Transform(); +} + +void InterpolatedTransformAboutPivot::Init(const gfx::Point& pivot, + InterpolatedTransform* xform) { + ui::Transform to_pivot; + ui::Transform from_pivot; + to_pivot.SetTranslate(-pivot.x(), -pivot.y()); + from_pivot.SetTranslate(pivot.x(), pivot.y()); + + scoped_ptr<InterpolatedTransform> pre_transform( + new InterpolatedConstantTransform(to_pivot)); + scoped_ptr<InterpolatedTransform> post_transform( + new InterpolatedConstantTransform(from_pivot)); + + pre_transform->SetChild(xform); + xform->SetChild(post_transform.release()); + transform_.reset(pre_transform.release()); +} + +} // namespace ui diff --git a/ui/gfx/interpolated_transform.h b/ui/gfx/interpolated_transform.h new file mode 100644 index 0000000..2a0a6b5 --- /dev/null +++ b/ui/gfx/interpolated_transform.h @@ -0,0 +1,195 @@ +// Copyright (c) 2011 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_INTERPOLATED_TRANSFORM_H_ +#define UI_GFX_INTERPOLATED_TRANSFORM_H_ +#pragma once + +#include "ui/gfx/transform.h" +#include "base/memory/scoped_ptr.h" +#include "ui/gfx/point.h" + +namespace ui { + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedTransform +// +// Abstract base class for transforms that animate over time. These +// interpolated transforms can be combined to allow for more sophisticated +// animations. For example, you might combine a rotation of 90 degrees between +// times 0 and 1, with a scale from 1 to 0.3 between times 0 and 0.25 and a +// scale from 0.3 to 1 from between times 0.75 and 1. +// +/////////////////////////////////////////////////////////////////////////////// +class InterpolatedTransform { + public: + InterpolatedTransform(); + // The interpolated transform varies only when t in (start_time, end_time). + // If t <= start_time, Interpolate(t) will return the initial transform, and + // if t >= end_time, Interpolate(t) will return the final transform. + InterpolatedTransform(float start_time, float end_time); + virtual ~InterpolatedTransform(); + + // Returns the interpolated transform at time t. Note: not virtual. + ui::Transform Interpolate(float t) const; + + // The Intepolate ultimately returns the product of our transform at time t + // and our child's transform at time t (if we have one). + // + // This function takes ownership of the passed InterpolatedTransform. + void SetChild(InterpolatedTransform* child); + + protected: + // Calculates the interpolated transform without considering our child. + virtual ui::Transform InterpolateButDoNotCompose(float t) const = 0; + + // If time in (start_time_, end_time_], this function linearly interpolates + // between start_value and end_value. More precisely it returns + // (1 - t) * start_value + t * end_value where + // t = (start_time_ - time) / (end_time_ - start_time_). + // If time < start_time_ it returns start_value, and if time >= end_time_ + // it returns end_value. + float ValueBetween(float time, float start_value, float end_value) const; + + private: + const float start_time_; + const float end_time_; + + // The child transform. If you consider an interpolated transform as a + // function of t. If, without a child, we are f(t), and our child is + // g(t), then with a child we become f'(t) = f(t) * g(t). Using a child + // transform, we can chain collections of transforms together. + scoped_ptr<InterpolatedTransform> child_; + + DISALLOW_COPY_AND_ASSIGN(InterpolatedTransform); +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedRotation +// +// Represents an animated rotation. +// +/////////////////////////////////////////////////////////////////////////////// +class InterpolatedRotation : public InterpolatedTransform { + public: + InterpolatedRotation(float start_degrees, float end_degrees); + InterpolatedRotation(float start_degrees, + float end_degrees, + float start_time, + float end_time); + virtual ~InterpolatedRotation(); + + protected: + virtual ui::Transform InterpolateButDoNotCompose(float t) const OVERRIDE; + + private: + const float start_degrees_; + const float end_degrees_; + + DISALLOW_COPY_AND_ASSIGN(InterpolatedRotation); +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedScale +// +// Represents an animated scale. +// +/////////////////////////////////////////////////////////////////////////////// +class InterpolatedScale : public InterpolatedTransform { + public: + InterpolatedScale(float start_scale, float end_scale); + InterpolatedScale(float start_scale, + float end_scale, + float start_time, + float end_time); + virtual ~InterpolatedScale(); + + protected: + virtual ui::Transform InterpolateButDoNotCompose(float t) const OVERRIDE; + + private: + const float start_scale_; + const float end_scale_; + + DISALLOW_COPY_AND_ASSIGN(InterpolatedScale); +}; + +class InterpolatedTranslation : public InterpolatedTransform { + public: + InterpolatedTranslation(const gfx::Point& start_pos, + const gfx::Point& end_pos); + InterpolatedTranslation(const gfx::Point& start_pos, + const gfx::Point& end_pos, + float start_time, + float end_time); + virtual ~InterpolatedTranslation(); + + protected: + virtual ui::Transform InterpolateButDoNotCompose(float t) const OVERRIDE; + + private: + const gfx::Point start_pos_; + const gfx::Point end_pos_; + + DISALLOW_COPY_AND_ASSIGN(InterpolatedTranslation); +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedConstantTransform +// +// Represents a transform that is constant over time. This is only useful when +// composed with other interpolated transforms. +// +// See InterpolatedTransformAboutPivot for an example of its usage. +// +/////////////////////////////////////////////////////////////////////////////// +class InterpolatedConstantTransform : public InterpolatedTransform { + public: + InterpolatedConstantTransform(const ui::Transform& transform); + virtual ~InterpolatedConstantTransform(); + + protected: + virtual ui::Transform InterpolateButDoNotCompose(float t) const OVERRIDE; + + private: + const ui::Transform transform_; + + DISALLOW_COPY_AND_ASSIGN(InterpolatedConstantTransform); +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedTransformAboutPivot +// +// Represents an animated transform with a transformed origin. Essentially, +// at each time, t, the interpolated transform is created by composing +// P * T * P^-1 where P is a constant transform to the new origin. +// +/////////////////////////////////////////////////////////////////////////////// +class InterpolatedTransformAboutPivot : public InterpolatedTransform { + public: + // Takes ownership of the passed transform. + InterpolatedTransformAboutPivot(const gfx::Point& pivot, + InterpolatedTransform* transform); + + // Takes ownership of the passed transform. + InterpolatedTransformAboutPivot(const gfx::Point& pivot, + InterpolatedTransform* transform, + float start_time, + float end_time); + virtual ~InterpolatedTransformAboutPivot(); + + protected: + virtual ui::Transform InterpolateButDoNotCompose(float t) const OVERRIDE; + + private: + void Init(const gfx::Point& pivot, InterpolatedTransform* transform); + + scoped_ptr<InterpolatedTransform> transform_; + + DISALLOW_COPY_AND_ASSIGN(InterpolatedTransformAboutPivot); +}; + +} // namespace ui + +#endif // UI_GFX_INTERPOLATED_TRANSFORM_H_ diff --git a/ui/gfx/interpolated_transform_unittest.cc b/ui/gfx/interpolated_transform_unittest.cc new file mode 100644 index 0000000..1aff853 --- /dev/null +++ b/ui/gfx/interpolated_transform_unittest.cc @@ -0,0 +1,112 @@ +// Copyright (c) 2011 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/interpolated_transform.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const static float EPSILON = 1e-6f; + +bool ApproximatelyEqual(float lhs, float rhs) { + if (lhs == 0) + return fabs(rhs) < EPSILON; + if (rhs == 0) + return fabs(lhs) < EPSILON; + return fabs(lhs - rhs) / std::max(fabs(rhs), fabs(lhs)) < EPSILON; +} + +bool ApproximatelyEqual(const ui::Transform& lhs, const ui::Transform& rhs) { + for (int i = 0; i < 9; ++i) { + if (!ApproximatelyEqual(lhs.matrix()[i], rhs.matrix()[i])) + return false; + } + return true; +} + +} // namespace + +TEST(InterpolatedTransformTest, InterpolatedRotation) { + ui::InterpolatedRotation interpolated_rotation(0, 100); + ui::InterpolatedRotation interpolated_rotation_diff_start_end( + 0, 100, 100, 200); + + for (int i = 0; i <= 100; ++i) { + ui::Transform rotation; + rotation.SetRotate(i); + ui::Transform interpolated = interpolated_rotation.Interpolate(i / 100.0f); + EXPECT_TRUE(ApproximatelyEqual(rotation, interpolated)); + interpolated = interpolated_rotation_diff_start_end.Interpolate(i + 100); + EXPECT_TRUE(ApproximatelyEqual(rotation, interpolated)); + } +} + +TEST(InterpolatedTransformTest, InterpolatedScale) { + ui::InterpolatedScale interpolated_scale(0, 100); + ui::InterpolatedScale interpolated_scale_diff_start_end( + 0, 100, 100, 200); + + for (int i = 0; i <= 100; ++i) { + ui::Transform scale; + scale.SetScale(i, i); + ui::Transform interpolated = interpolated_scale.Interpolate(i / 100.0f); + EXPECT_TRUE(ApproximatelyEqual(scale, interpolated)); + interpolated = interpolated_scale_diff_start_end.Interpolate(i + 100); + EXPECT_TRUE(ApproximatelyEqual(scale, interpolated)); + } +} + +TEST(InterpolatedTransformTest, InterpolatedTranslate) { + ui::InterpolatedTranslation interpolated_xform(gfx::Point(0, 0), + gfx::Point(100, 100)); + + ui::InterpolatedTranslation interpolated_xform_diff_start_end( + gfx::Point(0, 0), gfx::Point(100, 100), 100, 200); + + for (int i = 0; i <= 100; ++i) { + ui::Transform xform; + xform.SetTranslate(i, i); + ui::Transform interpolated = interpolated_xform.Interpolate(i / 100.0f); + EXPECT_TRUE(ApproximatelyEqual(xform, interpolated)); + interpolated = interpolated_xform_diff_start_end.Interpolate(i + 100); + EXPECT_TRUE(ApproximatelyEqual(xform, interpolated)); + } +} + +TEST(InterpolatedTransformTest, InterpolatedRotationAboutPivot) { + gfx::Point pivot(100, 100); + gfx::Point above_pivot(100, 200); + ui::InterpolatedRotation rot(0, 90); + ui::InterpolatedTransformAboutPivot interpolated_xform( + pivot, + new ui::InterpolatedRotation(0, 90)); + ui::Transform result = interpolated_xform.Interpolate(0.0f); + EXPECT_TRUE(ApproximatelyEqual(ui::Transform(), result)); + result = interpolated_xform.Interpolate(1.0f); + gfx::Point expected_result = pivot; + EXPECT_TRUE(result.TransformPoint(&pivot)); + EXPECT_EQ(expected_result, pivot); + expected_result = gfx::Point(0, 100); + EXPECT_TRUE(result.TransformPoint(&above_pivot)); + EXPECT_EQ(expected_result, above_pivot); +} + +TEST(InterpolatedTransformTest, InterpolatedScaleAboutPivot) { + gfx::Point pivot(100, 100); + gfx::Point above_pivot(100, 200); + ui::InterpolatedTransformAboutPivot interpolated_xform( + pivot, + new ui::InterpolatedScale(1, 2)); + ui::Transform result = interpolated_xform.Interpolate(0.0f); + EXPECT_TRUE(ApproximatelyEqual(ui::Transform(), result)); + result = interpolated_xform.Interpolate(1.0f); + gfx::Point expected_result = pivot; + EXPECT_TRUE(result.TransformPoint(&pivot)); + EXPECT_EQ(expected_result, pivot); + expected_result = gfx::Point(100, 300); + EXPECT_TRUE(result.TransformPoint(&above_pivot)); + EXPECT_EQ(expected_result, above_pivot); +} diff --git a/ui/ui_gfx.gypi b/ui/ui_gfx.gypi index c9907c3..2d33805bf 100644 --- a/ui/ui_gfx.gypi +++ b/ui/ui_gfx.gypi @@ -81,6 +81,8 @@ 'gfx/image/image_util.h', 'gfx/insets.cc', 'gfx/insets.h', + 'gfx/interpolated_transform.h', + 'gfx/interpolated_transform.cc', 'gfx/mac/nsimage_cache.h', 'gfx/mac/nsimage_cache.mm', 'gfx/mac/scoped_ns_disable_screen_updates.h', diff --git a/ui/ui_unittests.gypi b/ui/ui_unittests.gypi index 8ea6cb2..3fb1c4a 100644 --- a/ui/ui_unittests.gypi +++ b/ui/ui_unittests.gypi @@ -46,6 +46,7 @@ 'gfx/image/image_unittest_util.h', 'gfx/image/image_unittest_util.cc', 'gfx/insets_unittest.cc', + 'gfx/interpolated_transform_unittest.cc', 'gfx/rect_unittest.cc', 'gfx/run_all_unittests.cc', 'gfx/skbitmap_operations_unittest.cc', |