summaryrefslogtreecommitdiffstats
path: root/ui/gfx
diff options
context:
space:
mode:
authorvollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-15 01:12:55 +0000
committervollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-15 01:12:55 +0000
commit2fcafa0b3526619bbb10f0aebd7e6a22ee9f965b (patch)
tree36b374f9fe0318987049f6d257eee4a3d5443070 /ui/gfx
parenta6b0810ce805601284fe04881600ad4625de22ab (diff)
downloadchromium_src-2fcafa0b3526619bbb10f0aebd7e6a22ee9f965b.zip
chromium_src-2fcafa0b3526619bbb10f0aebd7e6a22ee9f965b.tar.gz
chromium_src-2fcafa0b3526619bbb10f0aebd7e6a22ee9f965b.tar.bz2
Add support for blending matrices.
This CL adds matrix blending functionality as outlined here: http://www.w3.org/TR/css3-3d-transforms/ BUG=159972 R=sky Review URL: https://codereview.chromium.org/11293199 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@167809 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/gfx')
-rw-r--r--ui/gfx/transform.cc203
-rw-r--r--ui/gfx/transform.h52
-rw-r--r--ui/gfx/transform_unittest.cc697
-rw-r--r--ui/gfx/transform_util.cc314
-rw-r--r--ui/gfx/transform_util.h36
5 files changed, 1245 insertions, 57 deletions
diff --git a/ui/gfx/transform.cc b/ui/gfx/transform.cc
index 1ca2021..e1d77c3 100644
--- a/ui/gfx/transform.cc
+++ b/ui/gfx/transform.cc
@@ -2,16 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// MSVC++ requires this to be set before any other includes to get M_PI.
+#define _USE_MATH_DEFINES
+
#include "ui/gfx/transform.h"
+#include <cmath>
+
#include "ui/gfx/point.h"
#include "ui/gfx/point3_f.h"
-#include "ui/gfx/rect_f.h"
+#include "ui/gfx/vector3d_f.h"
+#include "ui/gfx/rect.h"
#include "ui/gfx/safe_integer_conversions.h"
#include "ui/gfx/skia_util.h"
+#include "ui/gfx/transform_util.h"
namespace gfx {
+namespace {
+
+#define MGET(m, row, col) SkMScalarToDouble(m.get(row, col))
+#define MSET(m, row, col, value) m.set(row, col, SkMScalarToDouble(value))
+
+double TanDegrees(double degrees) {
+ double radians = degrees * M_PI / 180;
+ return std::tan(radians);
+}
+
+} // namespace
+
Transform::Transform() {
matrix_.reset();
}
@@ -41,11 +60,15 @@ void Transform::SetRotateAbout(const Point3F& axis, double degree) {
}
void Transform::SetScaleX(double x) {
- matrix_.set(0, 0, SkDoubleToMScalar(x));
+ MSET(matrix_, 0, 0, x);
}
void Transform::SetScaleY(double y) {
- matrix_.set(1, 1, SkDoubleToMScalar(y));
+ MSET(matrix_, 1, 1, y);
+}
+
+void Transform::SetScaleZ(double z) {
+ MSET(matrix_, 2, 2, z);
}
void Transform::SetScale(double x, double y) {
@@ -54,12 +77,22 @@ void Transform::SetScale(double x, double y) {
matrix_.get(2, 2));
}
+void Transform::SetScale3d(double x, double y, double z) {
+ matrix_.setScale(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(z));
+}
+
void Transform::SetTranslateX(double x) {
- matrix_.set(0, 3, SkDoubleToMScalar(x));
+ MSET(matrix_, 0, 3, x);
}
void Transform::SetTranslateY(double y) {
- matrix_.set(1, 3, SkDoubleToMScalar(y));
+ MSET(matrix_, 1, 3, y);
+}
+
+void Transform::SetTranslateZ(double z) {
+ MSET(matrix_, 2, 3, z);
}
void Transform::SetTranslate(double x, double y) {
@@ -68,9 +101,23 @@ void Transform::SetTranslate(double x, double y) {
matrix_.get(2, 3));
}
+void Transform::SetTranslate3d(double x, double y, double z) {
+ matrix_.setTranslate(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(z));
+}
+
+void Transform::SetSkewX(double angle) {
+ MSET(matrix_, 0, 1, TanDegrees(angle));
+}
+
+void Transform::SetSkewY(double angle) {
+ MSET(matrix_, 1, 0, TanDegrees(angle));
+}
+
void Transform::SetPerspectiveDepth(double depth) {
SkMatrix44 m;
- m.set(3, 2, SkDoubleToMScalar(-1 / depth));
+ MSET(m, 3, 2, -1.0 / depth);
matrix_ = m;
}
@@ -100,6 +147,14 @@ void Transform::ConcatScale(double x, double y) {
matrix_.postConcat(scale);
}
+void Transform::ConcatScale3d(double x, double y, double z) {
+ SkMatrix44 scale;
+ scale.setScale(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(z));
+ matrix_.postConcat(scale);
+}
+
void Transform::ConcatTranslate(double x, double y) {
SkMatrix44 translate;
translate.setTranslate(SkDoubleToMScalar(x),
@@ -108,12 +163,100 @@ void Transform::ConcatTranslate(double x, double y) {
matrix_.postConcat(translate);
}
+void Transform::ConcatTranslate3d(double x, double y, double z) {
+ SkMatrix44 translate;
+ translate.setTranslate(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(z));
+ matrix_.postConcat(translate);
+}
+
+void Transform::ConcatSkewX(double angle_x) {
+ Transform t;
+ t.SetSkewX(angle_x);
+ matrix_.postConcat(t.matrix_);
+}
+
+void Transform::ConcatSkewY(double angle_y) {
+ Transform t;
+ t.SetSkewY(angle_y);
+ matrix_.postConcat(t.matrix_);
+}
+
void Transform::ConcatPerspectiveDepth(double depth) {
SkMatrix44 m;
- m.set(3, 2, SkDoubleToMScalar(-1 / depth));
+ MSET(m, 3, 2, -1.0 / depth);
matrix_.postConcat(m);
}
+void Transform::PreconcatRotate(double degree) {
+ SkMatrix44 rot;
+ rot.setRotateDegreesAbout(SkDoubleToMScalar(0),
+ SkDoubleToMScalar(0),
+ SkDoubleToMScalar(1),
+ SkDoubleToMScalar(degree));
+ matrix_.preConcat(rot);
+}
+
+void Transform::PreconcatRotateAbout(const Point3F& axis, double degree) {
+ SkMatrix44 rot;
+ rot.setRotateDegreesAbout(SkDoubleToMScalar(axis.x()),
+ SkDoubleToMScalar(axis.y()),
+ SkDoubleToMScalar(axis.z()),
+ SkDoubleToMScalar(degree));
+ matrix_.preConcat(rot);
+}
+
+void Transform::PreconcatScale(double x, double y) {
+ SkMatrix44 scale;
+ scale.setScale(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(1));
+ matrix_.preConcat(scale);
+}
+
+void Transform::PreconcatScale3d(double x, double y, double z) {
+ SkMatrix44 scale;
+ scale.setScale(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(z));
+ matrix_.preConcat(scale);
+}
+
+void Transform::PreconcatTranslate(double x, double y) {
+ SkMatrix44 translate;
+ translate.setTranslate(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(0));
+ matrix_.preConcat(translate);
+}
+
+void Transform::PreconcatTranslate3d(double x, double y, double z) {
+ SkMatrix44 translate;
+ translate.setTranslate(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(z));
+ matrix_.preConcat(translate);
+}
+
+void Transform::PreconcatSkewX(double angle_x) {
+ Transform t;
+ t.SetSkewX(angle_x);
+ matrix_.preConcat(t.matrix_);
+}
+
+void Transform::PreconcatSkewY(double angle_y) {
+ Transform t;
+ t.SetSkewY(angle_y);
+ matrix_.preConcat(t.matrix_);
+}
+
+void Transform::PreconcatPerspectiveDepth(double depth) {
+ SkMatrix44 m;
+ MSET(m, 3, 2, -1.0 / depth);
+ matrix_.preConcat(m);
+}
+
void Transform::PreconcatTransform(const Transform& transform) {
if (!transform.matrix_.isIdentity()) {
matrix_.preConcat(transform.matrix_);
@@ -180,16 +323,38 @@ bool Transform::TransformRectReverse(RectF* rect) const {
return true;
}
+bool Transform::Blend(const Transform& from, double progress) {
+ if (progress <= 0.0) {
+ *this = from;
+ return true;
+ }
+
+ if (progress >= 1.0)
+ return true;
+
+ DecomposedTransform to_decomp;
+ DecomposedTransform from_decomp;
+ if (!DecomposeTransform(&to_decomp, *this) ||
+ !DecomposeTransform(&from_decomp, from))
+ return false;
+
+ if (!BlendDecomposedTransforms(&to_decomp, to_decomp, from_decomp, progress))
+ return false;
+
+ matrix_ = ComposeTransform(to_decomp).matrix();
+ return true;
+}
+
void Transform::TransformPointInternal(const SkMatrix44& xform,
Point3F& point) const {
- SkScalar p[4] = {
- SkDoubleToScalar(point.x()),
- SkDoubleToScalar(point.y()),
- SkDoubleToScalar(point.z()),
- SkDoubleToScalar(1)
+ SkMScalar p[4] = {
+ SkDoubleToMScalar(point.x()),
+ SkDoubleToMScalar(point.y()),
+ SkDoubleToMScalar(point.z()),
+ SkDoubleToMScalar(1)
};
- xform.map(p);
+ xform.mapMScalars(p);
if (p[3] != 1 && abs(p[3]) > 0) {
point.SetPoint(p[0] / p[3], p[1] / p[3], p[2]/ p[3]);
@@ -200,14 +365,14 @@ void Transform::TransformPointInternal(const SkMatrix44& xform,
void Transform::TransformPointInternal(const SkMatrix44& xform,
Point& point) const {
- SkScalar p[4] = {
- SkIntToScalar(point.x()),
- SkIntToScalar(point.y()),
- SkIntToScalar(0),
- SkIntToScalar(1)
+ SkMScalar p[4] = {
+ SkDoubleToMScalar(point.x()),
+ SkDoubleToMScalar(point.y()),
+ SkDoubleToMScalar(0),
+ SkDoubleToMScalar(1)
};
- xform.map(p);
+ xform.mapMScalars(p);
point.SetPoint(ToRoundedInt(p[0]), ToRoundedInt(p[1]));
}
diff --git a/ui/gfx/transform.h b/ui/gfx/transform.h
index 57fa903..45bc4da 100644
--- a/ui/gfx/transform.h
+++ b/ui/gfx/transform.h
@@ -42,12 +42,20 @@ class UI_EXPORT Transform {
// Sets the scaling parameters.
void SetScaleX(double x);
void SetScaleY(double y);
+ void SetScaleZ(double z);
void SetScale(double x, double y);
+ void SetScale3d(double x, double y, double z);
// Sets the translation parameters.
void SetTranslateX(double x);
void SetTranslateY(double y);
+ void SetTranslateZ(double z);
void SetTranslate(double x, double y);
+ void SetTranslate3d(double x, double y, double z);
+
+ // Sets the skew parameters.
+ void SetSkewX(double angle);
+ void SetSkewY(double angle);
// Creates a perspective matrix.
// Based on the 'perspective' operation from
@@ -62,13 +70,46 @@ class UI_EXPORT Transform {
// Applies scaling on current transform.
void ConcatScale(double x, double y);
+ void ConcatScale3d(double x, double y, double z);
// Applies translation on current transform.
void ConcatTranslate(double x, double y);
+ void ConcatTranslate3d(double x, double y, double z);
// Applies a perspective on current transform.
void ConcatPerspectiveDepth(double depth);
+ // Applies a skew on the current transform.
+ void ConcatSkewX(double angle_x);
+ void ConcatSkewY(double angle_y);
+
+ // Applies the current transformation on a rotation and assigns the result
+ // to |this|.
+ void PreconcatRotate(double degree);
+
+ // Applies the current transformation on an axis-angle rotation and assigns
+ // the result to |this|.
+ void PreconcatRotateAbout(const Point3F& point, double degree);
+
+ // Applies the current transformation on a scaling and assigns the result
+ // to |this|.
+ void PreconcatScale(double x, double y);
+ void PreconcatScale3d(double x, double y, double z);
+
+ // Applies the current transformation on a translation and assigns the result
+ // to |this|.
+ void PreconcatTranslate(double x, double y);
+ void PreconcatTranslate3d(double x, double y, double z);
+
+ // Applies the current transformation on a skew and assigns the result
+ // to |this|.
+ void PreconcatSkewX(double angle_x);
+ void PreconcatSkewY(double angle_y);
+
+ // Applies the current transformation on a perspective transform and assigns
+ // the result to |this|.
+ void PreconcatPerspectiveDepth(double depth);
+
// Applies a transformation on the current transformation
// (i.e. 'this = this * transform;').
void PreconcatTransform(const Transform& transform);
@@ -110,6 +151,17 @@ class UI_EXPORT Transform {
// transformed rect.
bool TransformRectReverse(RectF* rect) const;
+ // Decomposes |this| and |from|, interpolates the decomposed values, and
+ // sets |this| to the reconstituted result. Returns false if either matrix
+ // can't be decomposed. Uses routines described in this spec:
+ // http://www.w3.org/TR/css3-3d-transforms/.
+ //
+ // Note: this call is expensive since we need to decompose the transform. If
+ // you're going to be calling this rapidly (e.g., in an animation) you should
+ // decompose once using gfx::DecomposeTransforms and reuse your
+ // DecomposedTransform.
+ bool Blend(const Transform& from, double progress);
+
// Returns the underlying matrix.
const SkMatrix44& matrix() const { return matrix_; }
SkMatrix44& matrix() { return matrix_; }
diff --git a/ui/gfx/transform_unittest.cc b/ui/gfx/transform_unittest.cc
index 52bb1c6..9518c66 100644
--- a/ui/gfx/transform_unittest.cc
+++ b/ui/gfx/transform_unittest.cc
@@ -2,8 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// MSVC++ requires this to be set before any other includes to get M_PI.
+#define _USE_MATH_DEFINES
+
#include "ui/gfx/transform.h"
+#include <cmath>
#include <ostream>
#include <limits>
@@ -11,17 +15,85 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/point.h"
#include "ui/gfx/point3_f.h"
+#include "ui/gfx/transform_util.h"
+
+namespace gfx {
namespace {
-bool PointsAreNearlyEqual(const gfx::Point3F& lhs,
- const gfx::Point3F& rhs) {
+bool PointsAreNearlyEqual(const Point3F& lhs,
+ const Point3F& rhs) {
float epsilon = 0.0001f;
return lhs.SquaredDistanceTo(rhs) < epsilon;
}
+bool MatricesAreNearlyEqual(const Transform& lhs,
+ const Transform& rhs) {
+ float epsilon = 0.0001f;
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ if (std::abs(lhs.matrix().get(row, col) -
+ rhs.matrix().get(row, col)) > epsilon)
+ return false;
+ }
+ }
+ return true;
+}
+
+#define EXPECT_ROW1_EQ(a, b, c, d, transform) \
+ EXPECT_FLOAT_EQ((a), (transform).matrix().get(0, 0)); \
+ EXPECT_FLOAT_EQ((b), (transform).matrix().get(0, 1)); \
+ EXPECT_FLOAT_EQ((c), (transform).matrix().get(0, 2)); \
+ EXPECT_FLOAT_EQ((d), (transform).matrix().get(0, 3));
+
+#define EXPECT_ROW2_EQ(a, b, c, d, transform) \
+ EXPECT_FLOAT_EQ((a), (transform).matrix().get(1, 0)); \
+ EXPECT_FLOAT_EQ((b), (transform).matrix().get(1, 1)); \
+ EXPECT_FLOAT_EQ((c), (transform).matrix().get(1, 2)); \
+ EXPECT_FLOAT_EQ((d), (transform).matrix().get(1, 3));
+
+#define EXPECT_ROW3_EQ(a, b, c, d, transform) \
+ EXPECT_FLOAT_EQ((a), (transform).matrix().get(2, 0)); \
+ EXPECT_FLOAT_EQ((b), (transform).matrix().get(2, 1)); \
+ EXPECT_FLOAT_EQ((c), (transform).matrix().get(2, 2)); \
+ EXPECT_FLOAT_EQ((d), (transform).matrix().get(2, 3));
+
+#define EXPECT_ROW4_EQ(a, b, c, d, transform) \
+ EXPECT_FLOAT_EQ((a), (transform).matrix().get(3, 0)); \
+ EXPECT_FLOAT_EQ((b), (transform).matrix().get(3, 1)); \
+ EXPECT_FLOAT_EQ((c), (transform).matrix().get(3, 2)); \
+ EXPECT_FLOAT_EQ((d), (transform).matrix().get(3, 3)); \
+
+// Checking float values for equality close to zero is not robust using
+// EXPECT_FLOAT_EQ (see gtest documentation). So, to verify rotation matrices,
+// we must use a looser absolute error threshold in some places.
+#define EXPECT_ROW1_NEAR(a, b, c, d, transform, errorThreshold) \
+ EXPECT_NEAR((a), (transform).matrix().get(0, 0), (errorThreshold)); \
+ EXPECT_NEAR((b), (transform).matrix().get(0, 1), (errorThreshold)); \
+ EXPECT_NEAR((c), (transform).matrix().get(0, 2), (errorThreshold)); \
+ EXPECT_NEAR((d), (transform).matrix().get(0, 3), (errorThreshold));
+
+#define EXPECT_ROW2_NEAR(a, b, c, d, transform, errorThreshold) \
+ EXPECT_NEAR((a), (transform).matrix().get(1, 0), (errorThreshold)); \
+ EXPECT_NEAR((b), (transform).matrix().get(1, 1), (errorThreshold)); \
+ EXPECT_NEAR((c), (transform).matrix().get(1, 2), (errorThreshold)); \
+ EXPECT_NEAR((d), (transform).matrix().get(1, 3), (errorThreshold));
+
+#define EXPECT_ROW3_NEAR(a, b, c, d, transform, errorThreshold) \
+ EXPECT_NEAR((a), (transform).matrix().get(2, 0), (errorThreshold)); \
+ EXPECT_NEAR((b), (transform).matrix().get(2, 1), (errorThreshold)); \
+ EXPECT_NEAR((c), (transform).matrix().get(2, 2), (errorThreshold)); \
+ EXPECT_NEAR((d), (transform).matrix().get(2, 3), (errorThreshold));
+
+#ifdef SK_MSCALAR_IS_DOUBLE
+#define ERROR_THRESHOLD 1e-14
+#else
+#define ERROR_THRESHOLD 1e-7
+#endif
+#define LOOSE_ERROR_THRESHOLD 1e-7
+
TEST(XFormTest, Equality) {
- gfx::Transform lhs, rhs, interpolated;
+ Transform lhs, rhs, interpolated;
rhs.matrix().set3x3(1, 2, 3,
4, 5, 6,
7, 8, 9);
@@ -41,8 +113,8 @@ TEST(XFormTest, Equality) {
EXPECT_TRUE(rhs != interpolated);
}
}
- lhs = gfx::Transform();
- rhs = gfx::Transform();
+ lhs = Transform();
+ rhs = Transform();
for (int i = 1; i < 100; ++i) {
lhs.SetTranslate(i, i);
rhs.SetTranslate(-i, -i);
@@ -70,12 +142,12 @@ TEST(XFormTest, ConcatTranslate) {
10, 20 },
};
- gfx::Transform xform;
+ Transform xform;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
const TestCase& value = test_cases[i];
xform.ConcatTranslate(value.tx, value.ty);
- gfx::Point3F p1(value.x1, value.y1, 0);
- gfx::Point3F p2(value.x2, value.y2, 0);
+ Point3F p1(value.x1, value.y1, 0);
+ Point3F p2(value.x2, value.y2, 0);
xform.TransformPoint(p1);
if (value.tx == value.tx &&
value.ty == value.ty) {
@@ -97,12 +169,12 @@ TEST(XFormTest, ConcatScale) {
{ 1, std::numeric_limits<float>::quiet_NaN(), 1 }
};
- gfx::Transform xform;
+ Transform xform;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
const TestCase& value = test_cases[i];
xform.ConcatScale(value.scale, value.scale);
- gfx::Point3F p1(value.before, value.before, 0);
- gfx::Point3F p2(value.after, value.after, 0);
+ Point3F p1(value.before, value.before, 0);
+ Point3F p2(value.after, value.after, 0);
xform.TransformPoint(p1);
if (value.scale == value.scale) {
EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
@@ -126,12 +198,12 @@ TEST(XFormTest, ConcatRotate) {
{ 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0 }
};
- gfx::Transform xform;
+ Transform xform;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
const TestCase& value = test_cases[i];
xform.ConcatRotate(value.degrees);
- gfx::Point3F p1(value.x1, value.y1, 0);
- gfx::Point3F p2(value.x2, value.y2, 0);
+ Point3F p1(value.x1, value.y1, 0);
+ Point3F p2(value.x2, value.y2, 0);
xform.TransformPoint(p1);
if (value.degrees == value.degrees) {
EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
@@ -157,8 +229,8 @@ TEST(XFormTest, SetTranslate) {
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
const TestCase& value = test_cases[i];
for (int k = 0; k < 3; ++k) {
- gfx::Point3F p0, p1, p2;
- gfx::Transform xform;
+ Point3F p0, p1, p2;
+ Transform xform;
switch (k) {
case 0:
p1.SetPoint(value.x1, 0, 0);
@@ -204,8 +276,8 @@ TEST(XFormTest, SetScale) {
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
const TestCase& value = test_cases[i];
for (int k = 0; k < 3; ++k) {
- gfx::Point3F p0, p1, p2;
- gfx::Transform xform;
+ Point3F p0, p1, p2;
+ Transform xform;
switch (k) {
case 0:
p1.SetPoint(value.before, 0, 0);
@@ -256,11 +328,11 @@ TEST(XFormTest, SetRotate) {
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(set_rotate_cases); ++i) {
const SetRotateCase& value = set_rotate_cases[i];
- gfx::Point3F p0;
- gfx::Point3F p1(value.x, value.y, 0);
- gfx::Point3F p2(value.xprime, value.yprime, 0);
+ Point3F p0;
+ Point3F p1(value.x, value.y, 0);
+ Point3F p2(value.xprime, value.yprime, 0);
p0 = p1;
- gfx::Transform xform;
+ Transform xform;
xform.SetRotate(value.degree);
// just want to make sure that we don't crash in the case of NaN.
if (value.degree == value.degree) {
@@ -291,12 +363,12 @@ TEST(XFormTest, ConcatTranslate2D) {
10, 20},
};
- gfx::Transform xform;
+ Transform xform;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
const TestCase& value = test_cases[i];
xform.ConcatTranslate(value.tx, value.ty);
- gfx::Point p1(value.x1, value.y1);
- gfx::Point p2(value.x2, value.y2);
+ Point p1(value.x1, value.y1);
+ Point p2(value.x2, value.y2);
xform.TransformPoint(p1);
if (value.tx == value.tx &&
value.ty == value.ty) {
@@ -319,12 +391,12 @@ TEST(XFormTest, ConcatScale2D) {
{ 1, std::numeric_limits<float>::quiet_NaN(), 1}
};
- gfx::Transform xform;
+ Transform xform;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
const TestCase& value = test_cases[i];
xform.ConcatScale(value.scale, value.scale);
- gfx::Point p1(value.before, value.before);
- gfx::Point p2(value.after, value.after);
+ Point p1(value.before, value.before);
+ Point p2(value.after, value.after);
xform.TransformPoint(p1);
if (value.scale == value.scale) {
EXPECT_EQ(p1.x(), p2.x());
@@ -349,12 +421,12 @@ TEST(XFormTest, ConcatRotate2D) {
{ 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0}
};
- gfx::Transform xform;
+ Transform xform;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
const TestCase& value = test_cases[i];
xform.ConcatRotate(value.degrees);
- gfx::Point p1(value.x1, value.y1);
- gfx::Point p2(value.x2, value.y2);
+ Point p1(value.x1, value.y1);
+ Point p2(value.x2, value.y2);
xform.TransformPoint(p1);
if (value.degrees == value.degrees) {
EXPECT_EQ(p1.x(), p2.x());
@@ -383,8 +455,8 @@ TEST(XFormTest, SetTranslate2D) {
for (int j = -1; j < 2; ++j) {
for (int k = 0; k < 3; ++k) {
float epsilon = 0.0001f;
- gfx::Point p0, p1, p2;
- gfx::Transform xform;
+ Point p0, p1, p2;
+ Transform xform;
switch (k) {
case 0:
p1.SetPoint(value.x1, 0);
@@ -436,8 +508,8 @@ TEST(XFormTest, SetScale2D) {
for (int j = -1; j < 2; ++j) {
for (int k = 0; k < 3; ++k) {
float epsilon = 0.0001f;
- gfx::Point p0, p1, p2;
- gfx::Transform xform;
+ Point p0, p1, p2;
+ Transform xform;
switch (k) {
case 0:
p1.SetPoint(value.before, 0);
@@ -496,8 +568,8 @@ TEST(XFormTest, SetRotate2D) {
const SetRotateCase& value = set_rotate_cases[i];
for (int j = 1; j >= -1; --j) {
float epsilon = 0.1f;
- gfx::Point pt(value.x, value.y);
- gfx::Transform xform;
+ Point pt(value.x, value.y);
+ Transform xform;
// should be invariant to small floating point errors.
xform.SetRotate(value.degree + j * epsilon);
// just want to make sure that we don't crash in the case of NaN.
@@ -513,4 +585,553 @@ TEST(XFormTest, SetRotate2D) {
}
}
-} // namespace
+TEST(XFormTest, BlendTranslate) {
+ Transform from;
+ for (int i = 0; i < 10; ++i) {
+ Transform to;
+ to.SetTranslate3d(1, 1, 1);
+ double t = i / 9.0;
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_FLOAT_EQ(t, to.matrix().get(0, 3));
+ EXPECT_FLOAT_EQ(t, to.matrix().get(1, 3));
+ EXPECT_FLOAT_EQ(t, to.matrix().get(2, 3));
+ }
+}
+
+TEST(XFormTest, BlendRotate) {
+ Point3F axes[] = {
+ Point3F(1, 0, 0),
+ Point3F(0, 1, 0),
+ Point3F(0, 0, 1),
+ Point3F(1, 1, 1)
+ };
+ Transform from;
+ for (size_t index = 0; index < ARRAYSIZE_UNSAFE(axes); ++index) {
+ for (int i = 0; i < 10; ++i) {
+ Transform to;
+ to.SetRotateAbout(axes[index], 90);
+ double t = i / 9.0;
+ EXPECT_TRUE(to.Blend(from, t));
+
+ Transform expected;
+ expected.SetRotateAbout(axes[index], 90 * t);
+
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
+ }
+ }
+}
+
+TEST(XFormTest, CannotBlend180DegreeRotation) {
+ Point3F axes[] = {
+ Point3F(1, 0, 0),
+ Point3F(0, 1, 0),
+ Point3F(0, 0, 1),
+ Point3F(1, 1, 1)
+ };
+ Transform from;
+ for (size_t index = 0; index < ARRAYSIZE_UNSAFE(axes); ++index) {
+ Transform to;
+ to.SetRotateAbout(axes[index], 180);
+ EXPECT_FALSE(to.Blend(from, 0.5));
+ }
+}
+
+TEST(XFormTest, BlendScale) {
+ Transform from;
+ for (int i = 0; i < 10; ++i) {
+ Transform to;
+ to.SetScale3d(5, 4, 3);
+ double t = i / 9.0;
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_FLOAT_EQ(t * 4 + 1, to.matrix().get(0, 0));
+ EXPECT_FLOAT_EQ(t * 3 + 1, to.matrix().get(1, 1));
+ EXPECT_FLOAT_EQ(t * 2 + 1, to.matrix().get(2, 2));
+ }
+}
+
+TEST(XFormTest, BlendSkew) {
+ Transform from;
+ for (int i = 0; i < 2; ++i) {
+ Transform to;
+ to.SetSkewX(20);
+ to.PreconcatSkewY(10);
+ double t = i;
+ Transform expected;
+ expected.SetSkewX(t * 20);
+ expected.PreconcatSkewY(t * 10);
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
+ }
+}
+
+TEST(XFormTest, BlendPerspective) {
+ Transform from;
+ from.SetPerspectiveDepth(200);
+ for (int i = 0; i < 2; ++i) {
+ Transform to;
+ to.SetPerspectiveDepth(800);
+ double t = i;
+ Transform expected;
+ expected.SetPerspectiveDepth(t * 600 + 200);
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
+ }
+}
+
+TEST(XFormTest, BlendIdentity) {
+ Transform from;
+ Transform to;
+ EXPECT_TRUE(to.Blend(from, 0.5));
+ EXPECT_EQ(to, from);
+}
+
+TEST(XFormTest, CannotBlendSingularMatrix) {
+ Transform from;
+ Transform to;
+ to.matrix().set(1, 1, SkDoubleToMScalar(0));
+ EXPECT_FALSE(to.Blend(from, 0.5));
+}
+
+TEST(XFormTest, VerifyBlendForTranslation)
+{
+ Transform from;
+ from.PreconcatTranslate3d(100, 200, 100);
+
+ Transform to;
+
+ to.PreconcatTranslate3d(200, 100, 300);
+ to.Blend(from, 0);
+ EXPECT_EQ(from, to);
+
+ to = Transform();
+ to.PreconcatTranslate3d(200, 100, 300);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_EQ(1, 0, 0, 125, to);
+ EXPECT_ROW2_EQ(0, 1, 0, 175, to);
+ EXPECT_ROW3_EQ(0, 0, 1, 150, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatTranslate3d(200, 100, 300);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_EQ(1, 0, 0, 150, to);
+ EXPECT_ROW2_EQ(0, 1, 0, 150, to);
+ EXPECT_ROW3_EQ(0, 0, 1, 200, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatTranslate3d(200, 100, 300);
+ to.Blend(from, 1);
+ EXPECT_ROW1_EQ(1, 0, 0, 200, to);
+ EXPECT_ROW2_EQ(0, 1, 0, 100, to);
+ EXPECT_ROW3_EQ(0, 0, 1, 300, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+}
+
+TEST(XFormTest, VerifyBlendForScale)
+{
+ Transform from;
+ from.PreconcatScale3d(100, 200, 100);
+
+ Transform to;
+
+ to.PreconcatScale3d(200, 100, 300);
+ to.Blend(from, 0);
+ EXPECT_EQ(from, to);
+
+ to = Transform();
+ to.PreconcatScale3d(200, 100, 300);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_EQ(125, 0, 0, 0, to);
+ EXPECT_ROW2_EQ(0, 175, 0, 0, to);
+ EXPECT_ROW3_EQ(0, 0, 150, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatScale3d(200, 100, 300);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_EQ(150, 0, 0, 0, to);
+ EXPECT_ROW2_EQ(0, 150, 0, 0, to);
+ EXPECT_ROW3_EQ(0, 0, 200, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatScale3d(200, 100, 300);
+ to.Blend(from, 1);
+ EXPECT_ROW1_EQ(200, 0, 0, 0, to);
+ EXPECT_ROW2_EQ(0, 100, 0, 0, to);
+ EXPECT_ROW3_EQ(0, 0, 300, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+}
+
+TEST(XFormTest, VerifyBlendForSkewX)
+{
+ Transform from;
+ from.PreconcatSkewX(0);
+
+ Transform to;
+
+ to.PreconcatSkewX(45);
+ to.Blend(from, 0);
+ EXPECT_EQ(from, to);
+
+ to = Transform();
+ to.PreconcatSkewX(45);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_EQ(1, 0.5, 0, 0, to);
+ EXPECT_ROW2_EQ(0, 1, 0, 0, to);
+ EXPECT_ROW3_EQ(0, 0, 1, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatSkewX(45);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_EQ(1, 0.25, 0, 0, to);
+ EXPECT_ROW2_EQ(0, 1, 0, 0, to);
+ EXPECT_ROW3_EQ(0, 0, 1, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatSkewX(45);
+ to.Blend(from, 1);
+ EXPECT_ROW1_EQ(1, 1, 0, 0, to);
+ EXPECT_ROW2_EQ(0, 1, 0, 0, to);
+ EXPECT_ROW3_EQ(0, 0, 1, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+}
+
+TEST(XFormTest, VerifyBlendForSkewY)
+{
+ // NOTE CAREFULLY: Decomposition of skew and rotation terms of the matrix
+ // is inherently underconstrained, and so it does not always compute the
+ // originally intended skew parameters. The current implementation uses QR
+ // decomposition, which decomposes the shear into a rotation + non-uniform
+ // scale.
+ //
+ // It is unlikely that the decomposition implementation will need to change
+ // very often, so to get any test coverage, the compromise is to verify the
+ // exact matrix that the.Blend() operation produces.
+ //
+ // This problem also potentially exists for skewX, but the current QR
+ // decomposition implementation just happens to decompose those test
+ // matrices intuitively.
+ //
+ // Unfortunately, this case suffers from uncomfortably large precision
+ // error.
+
+ Transform from;
+ from.PreconcatSkewY(0);
+
+ Transform to;
+
+ to.PreconcatSkewY(45);
+ to.Blend(from, 0);
+ EXPECT_EQ(from, to);
+
+ to = Transform();
+ to.PreconcatSkewY(45);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_NEAR(1.0823489449280947471976333,
+ 0.0464370719145053845178239,
+ 0,
+ 0,
+ to,
+ LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.2152925909665224513123150,
+ 0.9541702441750861130032035,
+ 0,
+ 0,
+ to,
+ LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0, 0, 1, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatSkewY(45);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_NEAR(1.1152212925809066312865525,
+ 0.0676495144007326631996335,
+ 0,
+ 0,
+ to,
+ LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.4619397844342648662419037,
+ 0.9519009045724774464858342,
+ 0,
+ 0,
+ to,
+ LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0, 0, 1, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatSkewY(45);
+ to.Blend(from, 1);
+ EXPECT_ROW1_NEAR(1, 0, 0, 0, to, LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(1, 1, 0, 0, to, LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0, 0, 1, 0, to);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+}
+
+TEST(XFormTest, VerifyBlendForRotationAboutX)
+{
+ // Even though.Blending uses quaternions, axis-aligned rotations should.
+ // Blend the same with quaternions or Euler angles. So we can test
+ // rotation.Blending by comparing against manually specified matrices from
+ // Euler angles.
+
+ Transform from;
+ from.PreconcatRotateAbout(Point3F(1, 0, 0), 0);
+
+ Transform to;
+
+ to.PreconcatRotateAbout(Point3F(1, 0, 0), 90);
+ to.Blend(from, 0);
+ EXPECT_EQ(from, to);
+
+ double expectedRotationAngle = 22.5 * M_PI / 180.0;
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(1, 0, 0), 90);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_NEAR(1, 0, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0,
+ std::cos(expectedRotationAngle),
+ -std::sin(expectedRotationAngle),
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0,
+ std::sin(expectedRotationAngle),
+ std::cos(expectedRotationAngle),
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ expectedRotationAngle = 45 * M_PI / 180.0;
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(1, 0, 0), 90);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_NEAR(1, 0, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0,
+ std::cos(expectedRotationAngle),
+ -std::sin(expectedRotationAngle),
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0,
+ std::sin(expectedRotationAngle),
+ std::cos(expectedRotationAngle),
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(1, 0, 0), 90);
+ to.Blend(from, 1);
+ EXPECT_ROW1_NEAR(1, 0, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0, 0, -1, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0, 1, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+}
+
+TEST(XFormTest, VerifyBlendForRotationAboutY)
+{
+ Transform from;
+ from.PreconcatRotateAbout(Point3F(0, 1, 0), 0);
+
+ Transform to;
+
+ to.PreconcatRotateAbout(Point3F(0, 1, 0), 90);
+ to.Blend(from, 0);
+ EXPECT_EQ(from, to);
+
+ double expectedRotationAngle = 22.5 * M_PI / 180.0;
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(0, 1, 0), 90);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
+ 0,
+ std::sin(expectedRotationAngle),
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0, 1, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle),
+ 0,
+ std::cos(expectedRotationAngle),
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ expectedRotationAngle = 45 * M_PI / 180.0;
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(0, 1, 0), 90);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
+ 0,
+ std::sin(expectedRotationAngle),
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0, 1, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle),
+ 0,
+ std::cos(expectedRotationAngle),
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(0, 1, 0), 90);
+ to.Blend(from, 1);
+ EXPECT_ROW1_NEAR(0, 0, 1, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0, 1, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(-1, 0, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+}
+
+TEST(XFormTest, VerifyBlendForRotationAboutZ)
+{
+ Transform from;
+ from.PreconcatRotateAbout(Point3F(0, 0, 1), 0);
+
+ Transform to;
+
+ to.PreconcatRotateAbout(Point3F(0, 0, 1), 90);
+ to.Blend(from, 0);
+ EXPECT_EQ(from, to);
+
+ double expectedRotationAngle = 22.5 * M_PI / 180.0;
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(0, 0, 1), 90);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
+ -std::sin(expectedRotationAngle),
+ 0,
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle),
+ std::cos(expectedRotationAngle),
+ 0,
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0, 0, 1, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ expectedRotationAngle = 45 * M_PI / 180.0;
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(0, 0, 1), 90);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
+ -std::sin(expectedRotationAngle),
+ 0,
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle),
+ std::cos(expectedRotationAngle),
+ 0,
+ 0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0, 0, 1, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+
+ to = Transform();
+ to.PreconcatRotateAbout(Point3F(0, 0, 1), 90);
+ to.Blend(from, 1);
+ EXPECT_ROW1_NEAR(0, -1, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(1, 0, 0, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0, 0, 1, 0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0, 0, 0, 1, to);
+}
+
+TEST(XFormTest, VerifyBlendForCompositeTransform)
+{
+ // Verify that the.Blending was done with a decomposition in correct order
+ // by blending a composite transform. Using matrix x vector notation
+ // (Ax = b, where x is column vector), the ordering should be:
+ // perspective * translation * rotation * skew * scale
+ //
+ // It is not as important (or meaningful) to check intermediate
+ // interpolations; order of operations will be tested well enough by the
+ // end cases that are easier to specify.
+
+ Transform from;
+ Transform to;
+
+ Transform expectedEndOfAnimation;
+ expectedEndOfAnimation.PreconcatPerspectiveDepth(1);
+ expectedEndOfAnimation.PreconcatTranslate3d(10, 20, 30);
+ expectedEndOfAnimation.PreconcatRotateAbout(Point3F(0, 0, 1), 25);
+ expectedEndOfAnimation.PreconcatSkewY(45);
+ expectedEndOfAnimation.PreconcatScale3d(6, 7, 8);
+
+ to = expectedEndOfAnimation;
+ to.Blend(from, 0);
+ EXPECT_EQ(from, to);
+
+ to = expectedEndOfAnimation;
+ // We short circuit if blend is >= 1, so to check the numerics, we will
+ // check that we get close to what we expect when we're nearly done
+ // interpolating.
+ to.Blend(from, .99999);
+
+ // Recomposing the matrix results in a normalized matrix, so to verify we
+ // need to normalize the expectedEndOfAnimation before comparing elements.
+ // Normalizing means dividing everything by expectedEndOfAnimation.m44().
+ Transform normalizedExpectedEndOfAnimation = expectedEndOfAnimation;
+ Transform normalizationMatrix;
+ normalizationMatrix.matrix().set(
+ 0, 0, SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3, 3)));
+ normalizationMatrix.matrix().set(
+ 1, 1, SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3, 3)));
+ normalizationMatrix.matrix().set(
+ 2, 2, SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3, 3)));
+ normalizationMatrix.matrix().set(
+ 3, 3, SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3, 3)));
+ normalizedExpectedEndOfAnimation.PreconcatTransform(normalizationMatrix);
+
+ EXPECT_TRUE(MatricesAreNearlyEqual(normalizedExpectedEndOfAnimation, to));
+}
+
+TEST(XFormTest, SetScaleZ)
+{
+ Transform scaled;
+ scaled.SetScaleZ(2);
+ EXPECT_FLOAT_EQ(2, scaled.matrix().get(2, 2));
+}
+
+TEST(XFormTest, SetTranslateZ)
+{
+ Transform translated;
+ translated.SetTranslateZ(2);
+ EXPECT_FLOAT_EQ(2, translated.matrix().get(2, 3));
+}
+
+TEST(XFormTest, DecomposedTransformCtor)
+{
+ DecomposedTransform decomp;
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(0.0, decomp.translate[i]);
+ EXPECT_EQ(1.0, decomp.scale[i]);
+ EXPECT_EQ(0.0, decomp.skew[i]);
+ EXPECT_EQ(0.0, decomp.quaternion[i]);
+ EXPECT_EQ(0.0, decomp.perspective[i]);
+ }
+ EXPECT_EQ(1.0, decomp.quaternion[3]);
+ EXPECT_EQ(1.0, decomp.perspective[3]);
+ Transform identity;
+ Transform composed = ComposeTransform(decomp);
+ EXPECT_TRUE(MatricesAreNearlyEqual(identity, composed));
+}
+
+} // namespace
+
+} // namespace gfx
diff --git a/ui/gfx/transform_util.cc b/ui/gfx/transform_util.cc
index cae1124..9a1551a 100644
--- a/ui/gfx/transform_util.cc
+++ b/ui/gfx/transform_util.cc
@@ -4,10 +4,111 @@
#include "ui/gfx/transform_util.h"
+#include <cmath>
+
#include "ui/gfx/point.h"
namespace gfx {
+namespace {
+
+#define MGET(m, row, col) SkMScalarToDouble(m.get(row, col))
+#define MSET(m, row, col, value) m.set(row, col, SkMScalarToDouble(value))
+
+// TODO(vollick): Remove this once SkMatrix44::transpose has landed.
+SkMatrix44 Transpose(const SkMatrix44& m) {
+ SkMatrix44 to_return;
+ for (int i = 0; i < 4; ++i)
+ for (int j = 0; j < 4; ++j)
+ to_return.set(i, j, m.get(j, i));
+ return to_return;
+}
+
+double Length3(double v[3]) {
+ return std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+}
+
+void Scale3(double v[3], double scale) {
+ for (int i = 0; i < 3; ++i)
+ v[i] *= scale;
+}
+
+template <int n>
+double Dot(const double* a, const double* b) {
+ double toReturn = 0;
+ for (int i = 0; i < n; ++i)
+ toReturn += a[i] * b[i];
+ return toReturn;
+}
+
+template <int n>
+void Combine(double* out,
+ const double* a,
+ const double* b,
+ double scale_a,
+ double scale_b) {
+ for (int i = 0; i < n; ++i)
+ out[i] = a[i] * scale_a + b[i] * scale_b;
+}
+
+void Cross3(double out[3], double a[3], double b[3]) {
+ double x = a[1] * b[2] - a[2] * b[1];
+ double y = a[2] * b[0] - a[0] * b[2];
+ double z = a[0] * b[1] - a[1] * b[0];
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+}
+
+// Taken from http://www.w3.org/TR/css3-transforms/.
+bool Slerp(double out[4],
+ const double q1[4],
+ const double q2[4],
+ double progress) {
+ double product = Dot<4>(q1, q2);
+
+ // Clamp product to -1.0 <= product <= 1.0.
+ product = std::min(std::max(product, -1.0), 1.0);
+
+ const double epsilon = 1e-5;
+ if (std::abs(product - 1.0) < epsilon) {
+ for (int i = 0; i < 4; ++i)
+ out[i] = q1[i];
+ return true;
+ }
+
+ if (std::abs(product) < epsilon) {
+ // Rotation by 180 degrees. We'll fail. It's ambiguous how to interpolate.
+ return false;
+ }
+
+ double denom = std::sqrt(1 - product * product);
+ double theta = std::acos(product);
+ double w = std::sin(progress * theta) * (1 / denom);
+
+ double scale1 = std::cos(progress * theta) - product * w;
+ double scale2 = w;
+ Combine<4>(out, q1, q2, scale1, scale2);
+
+ return true;
+}
+
+// Returns false if the matrix cannot be normalized.
+bool Normalize(SkMatrix44& m) {
+ if (MGET(m, 3, 3) == 0.0)
+ // Cannot normalize.
+ return false;
+
+ double scale = 1.0 / MGET(m, 3, 3);
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ MSET(m, i, j, MGET(m, i, j) * scale);
+
+ return true;
+}
+
+} // namespace
+
Transform GetScaleTransform(const Point& anchor, float scale) {
Transform transform;
transform.ConcatScale(scale, scale);
@@ -16,4 +117,217 @@ Transform GetScaleTransform(const Point& anchor, float scale) {
return transform;
}
+DecomposedTransform::DecomposedTransform() {
+ translate[0] = translate[1] = translate[2] = 0.0;
+ scale[0] = scale[1] = scale[2] = 1.0;
+ skew[0] = skew[1] = skew[2] = 0.0;
+ perspective[0] = perspective[1] = perspective[2] = 0.0;
+ quaternion[0] = quaternion[1] = quaternion[2] = 0.0;
+ perspective[3] = quaternion[3] = 1.0;
+}
+
+bool BlendDecomposedTransforms(DecomposedTransform* out,
+ const DecomposedTransform& to,
+ const DecomposedTransform& from,
+ double progress) {
+ double scalea = progress;
+ double scaleb = 1.0 - progress;
+ Combine<3>(out->translate, to.translate, from.translate, scalea, scaleb);
+ Combine<3>(out->scale, to.scale, from.scale, scalea, scaleb);
+ Combine<3>(out->skew, to.skew, from.skew, scalea, scaleb);
+ Combine<4>(
+ out->perspective, to.perspective, from.perspective, scalea, scaleb);
+ return Slerp(out->quaternion, from.quaternion, to.quaternion, progress);
+}
+
+// Taken from http://www.w3.org/TR/css3-transforms/.
+bool DecomposeTransform(DecomposedTransform* decomp,
+ const Transform& transform) {
+ if (!decomp)
+ return false;
+
+ // We'll operate on a copy of the matrix.
+ SkMatrix44 matrix = transform.matrix();
+
+ // If we cannot normalize the matrix, then bail early as we cannot decompose.
+ if (!Normalize(matrix))
+ return false;
+
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ MSET(matrix, i, j, MGET(matrix, i, j) / MGET(matrix, 3, 3));
+
+ SkMatrix44 perspectiveMatrix = matrix;
+
+ for (int i = 0; i < 3; ++i)
+ MSET(perspectiveMatrix, 3, i, 0.0);
+
+ MSET(perspectiveMatrix, 3, 3, 1.0);
+
+ // If the perspective matrix is not invertible, we are also unable to
+ // decompose, so we'll bail early. Constant taken from SkMatrix44::invert.
+ if (std::abs(perspectiveMatrix.determinant()) < 1e-8)
+ return false;
+
+ if (MGET(matrix, 3, 0) != 0.0 ||
+ MGET(matrix, 3, 1) != 0.0 ||
+ MGET(matrix, 3, 2) != 0.0) {
+ // rhs is the right hand side of the equation.
+ SkMScalar rhs[4] = {
+ matrix.get(3, 0),
+ matrix.get(3, 1),
+ matrix.get(3, 2),
+ matrix.get(3, 3)
+ };
+
+ // Solve the equation by inverting perspectiveMatrix and multiplying
+ // rhs by the inverse.
+ SkMatrix44 inversePerspectiveMatrix;
+ if (!perspectiveMatrix.invert(&inversePerspectiveMatrix))
+ return false;
+
+ SkMatrix44 transposedInversePerspectiveMatrix =
+ Transpose(inversePerspectiveMatrix);
+
+ transposedInversePerspectiveMatrix.mapMScalars(rhs);
+
+ for (int i = 0; i < 4; ++i)
+ decomp->perspective[i] = rhs[i];
+
+ } else {
+ // No perspective.
+ for (int i = 0; i < 3; ++i)
+ decomp->perspective[i] = 0.0;
+ decomp->perspective[3] = 1.0;
+ }
+
+ for (int i = 0; i < 3; i++)
+ decomp->translate[i] = MGET(matrix, i, 3);
+
+ double row[3][3];
+ for (int i = 0; i < 3; i++)
+ for (int j = 0; j < 3; ++j)
+ row[i][j] = MGET(matrix, j, i);
+
+ // Compute X scale factor and normalize first row.
+ decomp->scale[0] = Length3(row[0]);
+ if (decomp->scale[0] != 0.0)
+ Scale3(row[0], 1.0 / decomp->scale[0]);
+
+ // Compute XY shear factor and make 2nd row orthogonal to 1st.
+ decomp->skew[0] = Dot<3>(row[0], row[1]);
+ Combine<3>(row[1], row[1], row[0], 1.0, -decomp->skew[0]);
+
+ // Now, compute Y scale and normalize 2nd row.
+ decomp->scale[1] = Length3(row[1]);
+ if (decomp->scale[1] != 0.0)
+ Scale3(row[1], 1.0 / decomp->scale[1]);
+
+ decomp->skew[0] /= decomp->scale[1];
+
+ // Compute XZ and YZ shears, orthogonalize 3rd row
+ decomp->skew[1] = Dot<3>(row[0], row[2]);
+ Combine<3>(row[2], row[2], row[0], 1.0, -decomp->skew[1]);
+ decomp->skew[2] = Dot<3>(row[1], row[2]);
+ Combine<3>(row[2], row[2], row[1], 1.0, -decomp->skew[2]);
+
+ // Next, get Z scale and normalize 3rd row.
+ decomp->scale[2] = Length3(row[2]);
+ if (decomp->scale[2] != 0.0)
+ Scale3(row[2], 1.0 / decomp->scale[2]);
+
+ decomp->skew[1] /= decomp->scale[2];
+ decomp->skew[2] /= decomp->scale[2];
+
+ // At this point, the matrix (in rows) is orthonormal.
+ // Check for a coordinate system flip. If the determinant
+ // is -1, then negate the matrix and the scaling factors.
+ double pdum3[3];
+ Cross3(pdum3, row[1], row[2]);
+ if (Dot<3>(row[0], pdum3) < 0) {
+ for (int i = 0; i < 3; i++) {
+ decomp->scale[i] *= -1.0;
+ for (int j = 0; j < 3; ++j)
+ row[i][j] *= -1.0;
+ }
+ }
+
+ decomp->quaternion[0] =
+ 0.5 * std::sqrt(std::max(1.0 + row[0][0] - row[1][1] - row[2][2], 0.0));
+ decomp->quaternion[1] =
+ 0.5 * std::sqrt(std::max(1.0 - row[0][0] + row[1][1] - row[2][2], 0.0));
+ decomp->quaternion[2] =
+ 0.5 * std::sqrt(std::max(1.0 - row[0][0] - row[1][1] + row[2][2], 0.0));
+ decomp->quaternion[3] =
+ 0.5 * std::sqrt(std::max(1.0 + row[0][0] + row[1][1] + row[2][2], 0.0));
+
+ if (row[2][1] > row[1][2])
+ decomp->quaternion[0] = -decomp->quaternion[0];
+ if (row[0][2] > row[2][0])
+ decomp->quaternion[1] = -decomp->quaternion[1];
+ if (row[1][0] > row[0][1])
+ decomp->quaternion[2] = -decomp->quaternion[2];
+
+ return true;
+}
+
+// Taken from http://www.w3.org/TR/css3-transforms/.
+Transform ComposeTransform(const DecomposedTransform& decomp) {
+ SkMatrix44 matrix;
+ for (int i = 0; i < 4; i++)
+ MSET(matrix, 3, i, decomp.perspective[i]);
+
+ SkMatrix44 tempTranslation;
+ tempTranslation.setTranslate(SkDoubleToMScalar(decomp.translate[0]),
+ SkDoubleToMScalar(decomp.translate[1]),
+ SkDoubleToMScalar(decomp.translate[2]));
+ matrix.preConcat(tempTranslation);
+
+ double x = decomp.quaternion[0];
+ double y = decomp.quaternion[1];
+ double z = decomp.quaternion[2];
+ double w = decomp.quaternion[3];
+
+ SkMatrix44 rotation_matrix;
+ MSET(rotation_matrix, 0, 0, 1.0 - 2.0 * (y * y + z * z));
+ MSET(rotation_matrix, 0, 1, 2.0 * (x * y - z * w));
+ MSET(rotation_matrix, 0, 2, 2.0 * (x * z + y * w));
+ MSET(rotation_matrix, 1, 0, 2.0 * (x * y + z * w));
+ MSET(rotation_matrix, 1, 1, 1.0 - 2.0 * (x * x + z * z));
+ MSET(rotation_matrix, 1, 2, 2.0 * (y * z - x * w));
+ MSET(rotation_matrix, 2, 0, 2.0 * (x * z - y * w));
+ MSET(rotation_matrix, 2, 1, 2.0 * (y * z + x * w));
+ MSET(rotation_matrix, 2, 2, 1.0 - 2.0 * (x * x + y * y));
+
+ matrix.preConcat(rotation_matrix);
+
+ SkMatrix44 temp;
+ if (decomp.skew[2]) {
+ MSET(temp, 1, 2, decomp.skew[2]);
+ matrix.preConcat(temp);
+ }
+
+ if (decomp.skew[1]) {
+ MSET(temp, 1, 2, 0);
+ MSET(temp, 0, 2, decomp.skew[1]);
+ matrix.preConcat(temp);
+ }
+
+ if (decomp.skew[0]) {
+ MSET(temp, 0, 2, 0);
+ MSET(temp, 0, 1, decomp.skew[0]);
+ matrix.preConcat(temp);
+ }
+
+ SkMatrix44 tempScale;
+ tempScale.setScale(SkDoubleToMScalar(decomp.scale[0]),
+ SkDoubleToMScalar(decomp.scale[1]),
+ SkDoubleToMScalar(decomp.scale[2]));
+ matrix.preConcat(tempScale);
+
+ Transform to_return;
+ to_return.matrix() = matrix;
+ return to_return;
+}
+
} // namespace ui
diff --git a/ui/gfx/transform_util.h b/ui/gfx/transform_util.h
index e898d70..b6da7b0 100644
--- a/ui/gfx/transform_util.h
+++ b/ui/gfx/transform_util.h
@@ -15,6 +15,42 @@ class Point;
// Returns a scale transform at |anchor| point.
UI_EXPORT Transform GetScaleTransform(const Point& anchor, float scale);
+// Contains the components of a factored transform. These components may be
+// blended and recomposed.
+struct UI_EXPORT DecomposedTransform {
+ // The default constructor initializes the components in such a way that
+ // if used with Compose below, will produce the identity transform.
+ DecomposedTransform();
+
+ double translate[3];
+ double scale[3];
+ double skew[3];
+ double perspective[4];
+ double quaternion[4];
+
+ // Copy and assign are allowed.
+};
+
+// Interpolates the decomposed components |to| with |from| using the
+// routines described in http://www.w3.org/TR/css3-3d-transform/.
+// |progress| is in the range [0, 1] (0 leaves |out| unchanged, and 1
+// assigns |from| to |out|).
+UI_EXPORT bool BlendDecomposedTransforms(DecomposedTransform* out,
+ const DecomposedTransform& to,
+ const DecomposedTransform& from,
+ double progress);
+
+// Decomposes this transform into its translation, scale, skew, perspective,
+// and rotation components following the routines detailed in this spec:
+// http://www.w3.org/TR/css3-3d-transforms/.
+UI_EXPORT bool DecomposeTransform(DecomposedTransform* out,
+ const Transform& transform);
+
+// Composes a transform from the given translation, scale, skew, prespective,
+// and rotation components following the routines detailed in this spec:
+// http://www.w3.org/TR/css3-3d-transforms/.
+UI_EXPORT Transform ComposeTransform(const DecomposedTransform& decomp);
+
} // namespace gfx
#endif // UI_GFX_TRANSFORM_UTIL_H_