summaryrefslogtreecommitdiffstats
path: root/ui/gfx
diff options
context:
space:
mode:
authorbacker@chromium.org <backer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-08 15:31:11 +0000
committerbacker@chromium.org <backer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-08 15:31:11 +0000
commit80248e3395ac8f5d52e8e290435cdce6482a6726 (patch)
tree397cbe07f35593f6c77c539dbddca331bf7439a5 /ui/gfx
parenta9f090b9d48e668ec4e155b247b2d7fe25fe0f01 (diff)
downloadchromium_src-80248e3395ac8f5d52e8e290435cdce6482a6726.zip
chromium_src-80248e3395ac8f5d52e8e290435cdce6482a6726.tar.gz
chromium_src-80248e3395ac8f5d52e8e290435cdce6482a6726.tar.bz2
Use SkMatrix44 for the underlying implementation of ui::Transform
BUG= TEST=ui_unittest Review URL: http://codereview.chromium.org/7044062 Patch from Ian Vollick <vollick@chromium.org>. git-svn-id: svn://svn.chromium.org/chrome/trunk/src@91855 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/gfx')
-rw-r--r--ui/gfx/canvas.h2
-rw-r--r--ui/gfx/canvas_skia.cc2
-rw-r--r--ui/gfx/canvas_skia.h2
-rw-r--r--ui/gfx/compositor/compositor_gl.cc25
-rw-r--r--ui/gfx/compositor/compositor_win.cc25
-rw-r--r--ui/gfx/compositor/layer_animator.cc50
-rw-r--r--ui/gfx/compositor/layer_animator.h5
-rw-r--r--ui/gfx/interpolated_transform_unittest.cc18
-rw-r--r--ui/gfx/point3.h63
-rw-r--r--ui/gfx/transform.cc169
-rw-r--r--ui/gfx/transform.h70
-rw-r--r--ui/gfx/transform_unittest.cc515
12 files changed, 812 insertions, 134 deletions
diff --git a/ui/gfx/canvas.h b/ui/gfx/canvas.h
index c83b797..5154965 100644
--- a/ui/gfx/canvas.h
+++ b/ui/gfx/canvas.h
@@ -214,8 +214,10 @@ class Canvas {
// returned by BeginPlatformPaint().
virtual void EndPlatformPaint() = 0;
+#if !defined(OS_MACOSX)
// Apply transformation on the canvas.
virtual void Transform(const ui::Transform& transform) = 0;
+#endif
// Create a texture ID that can be used for accelerated drawing.
virtual ui::TextureID GetTextureID() = 0;
diff --git a/ui/gfx/canvas_skia.cc b/ui/gfx/canvas_skia.cc
index 3d88276..160ca94 100644
--- a/ui/gfx/canvas_skia.cc
+++ b/ui/gfx/canvas_skia.cc
@@ -331,9 +331,11 @@ void CanvasSkia::EndPlatformPaint() {
skia::EndPlatformPaint(this);
}
+#if !defined(OS_MACOSX)
void CanvasSkia::Transform(const ui::Transform& transform) {
concat(transform.matrix());
}
+#endif
CanvasSkia* CanvasSkia::AsCanvasSkia() {
return this;
diff --git a/ui/gfx/canvas_skia.h b/ui/gfx/canvas_skia.h
index 4811a81..ae2d9ba 100644
--- a/ui/gfx/canvas_skia.h
+++ b/ui/gfx/canvas_skia.h
@@ -158,7 +158,9 @@ class CanvasSkia : public skia::PlatformCanvas,
int dest_x, int dest_y, int w, int h);
virtual gfx::NativeDrawingContext BeginPlatformPaint();
virtual void EndPlatformPaint();
+#if !defined(OS_MACOSX)
virtual void Transform(const ui::Transform& transform);
+#endif
virtual ui::TextureID GetTextureID();
virtual CanvasSkia* AsCanvasSkia();
virtual const CanvasSkia* AsCanvasSkia() const;
diff --git a/ui/gfx/compositor/compositor_gl.cc b/ui/gfx/compositor/compositor_gl.cc
index f8d9efb..d59d85f 100644
--- a/ui/gfx/compositor/compositor_gl.cc
+++ b/ui/gfx/compositor/compositor_gl.cc
@@ -279,30 +279,7 @@ void TextureGL::DrawInternal(const ui::TextureProgramGL& program,
t.ConcatScale(2.0f/window_size.width(), 2.0f/window_size.height());
GLfloat m[16];
- const SkMatrix& matrix = t.matrix();
-
- // Convert 3x3 view transform matrix (row major) into 4x4 GL matrix (column
- // major). Assume 2-D rotations/translations restricted to XY plane.
-
- m[ 0] = matrix[0];
- m[ 1] = matrix[3];
- m[ 2] = 0;
- m[ 3] = matrix[6];
-
- m[ 4] = matrix[1];
- m[ 5] = matrix[4];
- m[ 6] = 0;
- m[ 7] = matrix[7];
-
- m[ 8] = 0;
- m[ 9] = 0;
- m[10] = 1;
- m[11] = 0;
-
- m[12] = matrix[2];
- m[13] = matrix[5];
- m[14] = 0;
- m[15] = matrix[8];
+ t.matrix().asColMajorf(m);
static const GLfloat vertices[] = { -1., -1., +0., +0., +1.,
+1., -1., +0., +1., +1.,
diff --git a/ui/gfx/compositor/compositor_win.cc b/ui/gfx/compositor/compositor_win.cc
index 19b91b5..7518108 100644
--- a/ui/gfx/compositor/compositor_win.cc
+++ b/ui/gfx/compositor/compositor_win.cc
@@ -339,21 +339,15 @@ void CompositorWin::Init() {
void CompositorWin::UpdatePerspective(const ui::Transform& transform,
const gfx::Size& view_size) {
- // Apply transform from view.
- const SkMatrix& sk_matrix(transform.matrix());
- // Use -1 * kMTransY for y-translation as origin for views is upper left.
- D3DXMATRIX transform_matrix(
- // row 1
- sk_matrix[SkMatrix::kMScaleX], sk_matrix[SkMatrix::kMSkewX], 0.0f,
- sk_matrix[SkMatrix::kMPersp0],
- // row 2
- sk_matrix[SkMatrix::kMSkewY], sk_matrix[SkMatrix::kMScaleY], 0.0f,
- sk_matrix[SkMatrix::kMPersp1],
- // row 3
- 0.0f, 0.0f, 1.0f, sk_matrix[SkMatrix::kMPersp2],
- // row 4.
- sk_matrix[SkMatrix::kMTransX], -sk_matrix[SkMatrix::kMTransY], 0.0f,
- 1.0f);
+ float transform_data_buffer[16];
+ transform.matrix().asColMajorf(transform_data_buffer);
+ D3DXMATRIX transform_matrix(&transform_data_buffer[0]);
+ std::swap(transform_matrix._12, transform_matrix._21);
+ std::swap(transform_matrix._13, transform_matrix._31);
+ std::swap(transform_matrix._23, transform_matrix._32);
+
+ // Different coordinate system; flip the y.
+ transform_matrix._42 *= -1;
// Scale so x and y are from 0-2.
D3DXMATRIX scale_matrix;
@@ -380,6 +374,7 @@ void CompositorWin::UpdatePerspective(const ui::Transform& transform,
D3DXMATRIX wvp = transform_matrix * scale_matrix * translate_matrix * view *
projection_matrix;
+
fx_->GetVariableByName("gWVP")->AsMatrix()->SetMatrix(wvp);
}
diff --git a/ui/gfx/compositor/layer_animator.cc b/ui/gfx/compositor/layer_animator.cc
index 9122975..49b4440 100644
--- a/ui/gfx/compositor/layer_animator.cc
+++ b/ui/gfx/compositor/layer_animator.cc
@@ -13,6 +13,22 @@
#include "ui/gfx/transform.h"
#include "ui/gfx/rect.h"
+namespace {
+
+void SetMatrixElement(SkMatrix44& matrix, int index, SkMScalar value) {
+ int row = index / 4;
+ int col = index % 4;
+ matrix.set(row, col, value);
+}
+
+SkMScalar GetMatrixElement(const SkMatrix44& matrix, int index) {
+ int row = index / 4;
+ int col = index % 4;
+ return matrix.get(row, col);
+}
+
+} // anonymous namespace
+
namespace ui {
LayerAnimator::LayerAnimator(Layer* layer)
@@ -51,21 +67,15 @@ void LayerAnimator::AnimateToPoint(const gfx::Point& target) {
void LayerAnimator::AnimateTransform(const Transform& transform) {
StopAnimating(TRANSFORM);
const Transform& layer_transform = layer_->transform();
- bool all_equal = true;
- // TODO: replace with == when we Transform supports ==.
- for (int i = 0; i < 9; ++i) {
- if (transform.matrix()[i] != layer_transform.matrix()[i]) {
- all_equal = false;
- break;
- }
- }
- if (all_equal)
+ if (transform == layer_transform)
return; // Already there.
Element& element = elements_[TRANSFORM];
- for (int i = 0; i < 9; ++i) {
- element.params.transform.start[i] = layer_transform.matrix()[i];
- element.params.transform.target[i] = transform.matrix()[i];
+ for (int i = 0; i < 16; ++i) {
+ element.params.transform.start[i] =
+ GetMatrixElement(layer_transform.matrix(), i);
+ element.params.transform.target[i] =
+ GetMatrixElement(transform.matrix(), i);
}
element.animation = CreateAndStartAnimation();
}
@@ -90,10 +100,11 @@ void LayerAnimator::AnimationProgressed(const ui::Animation* animation) {
case TRANSFORM: {
Transform transform;
- for (int i = 0; i < 9; ++i) {
- transform.matrix()[i] = e->second.animation->CurrentValueBetween(
- e->second.params.transform.start[i],
- e->second.params.transform.target[i]);
+ for (int i = 0; i < 16; ++i) {
+ SkMScalar value = e->second.animation->CurrentValueBetween(
+ e->second.params.transform.start[i],
+ e->second.params.transform.target[i]);
+ SetMatrixElement(transform.matrix(), i, value);
}
layer_->set_transform(transform);
break;
@@ -121,8 +132,11 @@ void LayerAnimator::AnimationEnded(const ui::Animation* animation) {
case TRANSFORM: {
Transform transform;
- for (int i = 0; i < 9; ++i)
- transform.matrix()[i] = e->second.params.transform.target[i];
+ for (int i = 0; i < 16; ++i) {
+ SetMatrixElement(transform.matrix(),
+ i,
+ e->second.params.transform.target[i]);
+ }
layer_->set_transform(transform);
break;
}
diff --git a/ui/gfx/compositor/layer_animator.h b/ui/gfx/compositor/layer_animator.h
index da86766..c30ba87 100644
--- a/ui/gfx/compositor/layer_animator.h
+++ b/ui/gfx/compositor/layer_animator.h
@@ -11,6 +11,7 @@
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "third_party/skia/include/core/SkScalar.h"
+#include "third_party/skia/include/utils/SkMatrix44.h"
#include "ui/base/animation/animation_delegate.h"
#include "ui/base/animation/tween.h"
#include "ui/gfx/point.h"
@@ -68,8 +69,8 @@ class LayerAnimator : public ui::AnimationDelegate {
// Parameters used whe animating the transform.
struct TransformParams {
// TODO: make 4x4 whe Transform is updated.
- SkScalar start[9];
- SkScalar target[9];
+ SkMScalar start[16];
+ SkMScalar target[16];
};
union Params {
diff --git a/ui/gfx/interpolated_transform_unittest.cc b/ui/gfx/interpolated_transform_unittest.cc
index 1aff853..08e7279 100644
--- a/ui/gfx/interpolated_transform_unittest.cc
+++ b/ui/gfx/interpolated_transform_unittest.cc
@@ -20,9 +20,13 @@ bool ApproximatelyEqual(float lhs, float rhs) {
}
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;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ if (!ApproximatelyEqual(lhs.matrix().get(i, j),
+ rhs.matrix().get(i, j))) {
+ return false;
+ }
+ }
}
return true;
}
@@ -87,10 +91,10 @@ TEST(InterpolatedTransformTest, InterpolatedRotationAboutPivot) {
EXPECT_TRUE(ApproximatelyEqual(ui::Transform(), result));
result = interpolated_xform.Interpolate(1.0f);
gfx::Point expected_result = pivot;
- EXPECT_TRUE(result.TransformPoint(&pivot));
+ result.TransformPoint(pivot);
EXPECT_EQ(expected_result, pivot);
expected_result = gfx::Point(0, 100);
- EXPECT_TRUE(result.TransformPoint(&above_pivot));
+ result.TransformPoint(above_pivot);
EXPECT_EQ(expected_result, above_pivot);
}
@@ -104,9 +108,9 @@ TEST(InterpolatedTransformTest, InterpolatedScaleAboutPivot) {
EXPECT_TRUE(ApproximatelyEqual(ui::Transform(), result));
result = interpolated_xform.Interpolate(1.0f);
gfx::Point expected_result = pivot;
- EXPECT_TRUE(result.TransformPoint(&pivot));
+ result.TransformPoint(pivot);
EXPECT_EQ(expected_result, pivot);
expected_result = gfx::Point(100, 300);
- EXPECT_TRUE(result.TransformPoint(&above_pivot));
+ result.TransformPoint(above_pivot);
EXPECT_EQ(expected_result, above_pivot);
}
diff --git a/ui/gfx/point3.h b/ui/gfx/point3.h
new file mode 100644
index 0000000..d84aa07
--- /dev/null
+++ b/ui/gfx/point3.h
@@ -0,0 +1,63 @@
+// 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_POINT3_H_
+#define UI_GFX_POINT3_H_
+#pragma once
+
+#include <cmath>
+
+#include "ui/gfx/point.h"
+
+namespace gfx {
+
+// A point has an x, y and z coordinate.
+class Point3f {
+ public:
+ Point3f() : x_(0), y_(0), z_(0) {}
+
+ Point3f(float x, float y, float z) : x_(x), y_(y), z_(z) {}
+
+ Point3f(const Point& point) : x_(point.x()), y_(point.y()), z_(0) {}
+
+ ~Point3f() {}
+
+ float x() const { return x_; }
+ float y() const { return y_; }
+ float z() const { return z_; }
+
+ void set_x(float x) { x_ = x; }
+ void set_y(float y) { y_ = y; }
+ void set_z(float z) { z_ = z; }
+
+ void SetPoint(float x, float y, float z) {
+ x_ = x;
+ y_ = y;
+ z_ = z;
+ }
+
+ // Returns the squared euclidean distance between two points.
+ float SquaredDistanceTo(const Point3f& other) const {
+ float dx = x_ - other.x_;
+ float dy = y_ - other.y_;
+ float dz = z_ - other.z_;
+ return dx * dx + dy * dy + dz * dz;
+ }
+
+ Point AsPoint() const {
+ return Point(static_cast<int>(std::floor(x_)),
+ static_cast<int>(std::floor(y_)));
+ }
+
+ private:
+ float x_;
+ float y_;
+ float z_;
+
+ // copy/assign are allowed.
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_POINT3_H_
diff --git a/ui/gfx/transform.cc b/ui/gfx/transform.cc
index bfd137a..5d4463e 100644
--- a/ui/gfx/transform.cc
+++ b/ui/gfx/transform.cc
@@ -3,13 +3,33 @@
// found in the LICENSE file.
#include "ui/gfx/transform.h"
-
-#include <cmath>
-
-#include "ui/gfx/point.h"
+#include "ui/gfx/point3.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/skia_util.h"
+namespace {
+
+static int SymmetricRound(float x) {
+ return static_cast<int>(
+ x > 0
+ ? std::floor(x + 0.5f)
+ : std::ceil(x - 0.5f));
+}
+
+static const float EPSILON = 1e-6f;
+
+static bool ApproximatelyEqual(float a, float b) {
+ if (a == 0) {
+ return fabs(b) < EPSILON;
+ }
+ if (b == 0) {
+ return fabs(a) < EPSILON;
+ }
+ return fabs(a - b) / std::max(fabs(a), fabs(b)) < EPSILON;
+}
+
+} // namespace
+
namespace ui {
Transform::Transform() {
@@ -18,97 +38,164 @@ Transform::Transform() {
Transform::~Transform() {}
+bool Transform::operator==(const Transform& rhs) const {
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ if (!ApproximatelyEqual(matrix_.get(i,j), rhs.matrix_.get(i,j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool Transform::operator!=(const Transform& rhs) const {
+ return !(*this == rhs);
+}
+
void Transform::SetRotate(float degree) {
- matrix_.setRotate(SkFloatToScalar(degree));
+ matrix_.setRotateDegreesAbout(0, 0, 1, SkFloatToScalar(degree));
}
void Transform::SetScaleX(float x) {
- matrix_.setScaleX(SkFloatToScalar(x));
+ matrix_.set(0, 0, SkFloatToScalar(x));
}
void Transform::SetScaleY(float y) {
- matrix_.setScaleY(SkFloatToScalar(y));
+ matrix_.set(1, 1, SkFloatToScalar(y));
}
void Transform::SetScale(float x, float y) {
- matrix_.setScale(SkFloatToScalar(x), SkFloatToScalar(y));
+ matrix_.setScale(
+ SkFloatToScalar(x),
+ SkFloatToScalar(y),
+ matrix_.get(2, 2));
}
void Transform::SetTranslateX(float x) {
- matrix_.setTranslateX(SkFloatToScalar(x));
+ matrix_.set(0, 3, SkFloatToScalar(x));
}
void Transform::SetTranslateY(float y) {
- matrix_.setTranslateY(SkFloatToScalar(y));
+ matrix_.set(1, 3, SkFloatToScalar(y));
}
void Transform::SetTranslate(float x, float y) {
- matrix_.setTranslate(SkFloatToScalar(x), SkFloatToScalar(y));
+ matrix_.setTranslate(
+ SkFloatToScalar(x),
+ SkFloatToScalar(y),
+ matrix_.get(2, 3));
}
void Transform::ConcatRotate(float degree) {
- matrix_.postRotate(SkFloatToScalar(degree));
+ SkMatrix44 rot;
+ rot.setRotateDegreesAbout(0, 0, 1, SkFloatToScalar(degree));
+ matrix_.postConcat(rot);
}
void Transform::ConcatScale(float x, float y) {
- matrix_.postScale(SkFloatToScalar(x), SkFloatToScalar(y));
+ SkMatrix44 scale;
+ scale.setScale(SkFloatToScalar(x), SkFloatToScalar(y), 1);
+ matrix_.postConcat(scale);
}
void Transform::ConcatTranslate(float x, float y) {
- matrix_.postTranslate(SkFloatToScalar(x), SkFloatToScalar(y));
+ SkMatrix44 translate;
+ translate.setTranslate(SkFloatToScalar(x), SkFloatToScalar(y), 0);
+ matrix_.postConcat(translate);
}
-bool Transform::PreconcatTransform(const Transform& transform) {
- return matrix_.setConcat(matrix_, transform.matrix_);
+void Transform::PreconcatTransform(const Transform& transform) {
+ if (!transform.matrix_.isIdentity()) {
+ matrix_.preConcat(transform.matrix_);
+ }
}
-bool Transform::ConcatTransform(const Transform& transform) {
- return matrix_.setConcat(transform.matrix_, matrix_);
+void Transform::ConcatTransform(const Transform& transform) {
+ if (!transform.matrix_.isIdentity()) {
+ matrix_.postConcat(transform.matrix_);
+ }
}
bool Transform::HasChange() const {
return !matrix_.isIdentity();
}
-bool Transform::TransformPoint(gfx::Point* point) const {
- SkPoint skp;
- matrix_.mapXY(SkIntToScalar(point->x()), SkIntToScalar(point->y()), &skp);
- point->SetPoint(static_cast<int>(std::floor(skp.fX)),
- static_cast<int>(std::floor(skp.fY)));
+void Transform::TransformPoint(gfx::Point& point) const {
+ TransformPointInternal(matrix_, point);
+}
+
+void Transform::TransformPoint(gfx::Point3f& point) const {
+ TransformPointInternal(matrix_, point);
+}
+
+bool Transform::TransformPointReverse(gfx::Point& point) const {
+ // TODO(sad): Try to avoid trying to invert the matrix.
+ SkMatrix44 inverse;
+ if (!matrix_.invert(&inverse))
+ return false;
+
+ TransformPointInternal(inverse, point);
return true;
}
-bool Transform::TransformPointReverse(gfx::Point* point) const {
- SkMatrix inverse;
+bool Transform::TransformPointReverse(gfx::Point3f& point) const {
// TODO(sad): Try to avoid trying to invert the matrix.
- if (matrix_.invert(&inverse)) {
- SkPoint skp;
- inverse.mapXY(SkIntToScalar(point->x()), SkIntToScalar(point->y()), &skp);
- point->SetPoint(static_cast<int>(std::floor(skp.fX)),
- static_cast<int>(std::floor(skp.fY)));
- return true;
- }
- return false;
+ SkMatrix44 inverse;
+ if (!matrix_.invert(&inverse))
+ return false;
+
+ TransformPointInternal(inverse, point);
+ return true;
}
-bool Transform::TransformRect(gfx::Rect* rect) const {
+void Transform::TransformRect(gfx::Rect* rect) const {
SkRect src = gfx::RectToSkRect(*rect);
- if (!matrix_.mapRect(&src))
- return false;
+ const SkMatrix& matrix = matrix_;
+ matrix.mapRect(&src);
*rect = gfx::SkRectToRect(src);
- return true;
}
bool Transform::TransformRectReverse(gfx::Rect* rect) const {
- SkMatrix inverse;
+ SkMatrix44 inverse;
if (!matrix_.invert(&inverse))
return false;
-
+ const SkMatrix& matrix = inverse;
SkRect src = gfx::RectToSkRect(*rect);
- if (!inverse.mapRect(&src))
- return false;
+ matrix.mapRect(&src);
*rect = gfx::SkRectToRect(src);
return true;
}
+void Transform::TransformPointInternal(const SkMatrix44& xform,
+ gfx::Point3f& point) const {
+ SkScalar p[4] = {
+ SkFloatToScalar(point.x()),
+ SkFloatToScalar(point.y()),
+ SkFloatToScalar(point.z()),
+ 1 };
+
+ xform.map(p);
+
+ if (p[3] != 1 && abs(p[3]) > 0) {
+ point.SetPoint(p[0] / p[3], p[1] / p[3], p[2]/ p[3]);
+ } else {
+ point.SetPoint(p[0], p[1], p[2]);
+ }
+}
+
+void Transform::TransformPointInternal(const SkMatrix44& xform,
+ gfx::Point& point) const {
+ SkScalar p[4] = {
+ SkIntToScalar(point.x()),
+ SkIntToScalar(point.y()),
+ 0,
+ 1 };
+
+ xform.map(p);
+
+ point.SetPoint(SymmetricRound(p[0]),
+ SymmetricRound(p[1]));
+}
+
} // namespace ui
diff --git a/ui/gfx/transform.h b/ui/gfx/transform.h
index c3f86ef..1482fb7 100644
--- a/ui/gfx/transform.h
+++ b/ui/gfx/transform.h
@@ -6,25 +6,26 @@
#define UI_GFX_TRANSFORM_H_
#pragma once
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "third_party/skia/include/core/SkMatrix.h"
+#include "third_party/skia/include/utils/SkMatrix44.h"
namespace gfx {
-class Point;
class Rect;
+class Point;
+class Point3f;
}
namespace ui {
-// 3x3 transformation matrix. Transform is cheap and explicitly allows
+// 4x4 transformation matrix. Transform is cheap and explicitly allows
// copy/assign.
-// TODO: make this a 4x4.
class Transform {
public:
Transform();
~Transform();
+ bool operator==(const Transform& rhs) const;
+ bool operator!=(const Transform& rhs) const;
+
// NOTE: The 'Set' functions overwrite the previously set transformation
// parameters. The 'Concat' functions apply a transformation (e.g. rotation,
// scale, translate) on top of the existing transforms, instead of overwriting
@@ -57,44 +58,59 @@ class Transform {
void ConcatTranslate(float x, float y);
// Applies a transformation on the current transformation
- // (i.e. 'this = this * transform;'). Returns true if the result can be
- // represented.
- bool PreconcatTransform(const Transform& transform);
+ // (i.e. 'this = this * transform;').
+ void PreconcatTransform(const Transform& transform);
// Applies a transformation on the current transformation
- // (i.e. 'this = transform * this;'). Returns true if the result can be
- // represented.
- bool ConcatTransform(const Transform& transform);
+ // (i.e. 'this = transform * this;').
+ void ConcatTransform(const Transform& transform);
// Does the transformation change anything?
bool HasChange() const;
// Applies the transformation on the point. Returns true if the point is
// transformed successfully.
- bool TransformPoint(gfx::Point* point) const;
+ void TransformPoint(gfx::Point3f& point) const;
- // Applies the reverse transformation on the point. Returns true if the point
- // is transformed successfully.
- bool TransformPointReverse(gfx::Point* point) const;
-
- // Applies transformation on the rectangle. Returns true if the rectangle is
- // transformed successfully.
- bool TransformRect(gfx::Rect* rect) const;
-
- // Applies the reverse transformation on the rectangle. Returns true if the
- // rectangle is transformed successfully.
+ // Applies the transformation on the point. Returns true if the point is
+ // transformed successfully. Rounds the result to the nearest point.
+ void TransformPoint(gfx::Point& point) const;
+
+ // Applies the reverse transformation on the point. Returns true if the
+ // transformation can be inverted.
+ bool TransformPointReverse(gfx::Point3f& point) const;
+
+ // Applies the reverse transformation on the point. Returns true if the
+ // transformation can be inverted. Rounds the result to the nearest point.
+ bool TransformPointReverse(gfx::Point& point) const;
+
+ // Applies transformation on the rectangle. Returns true if the transformed
+ // rectangle was axis aligned. If it returns false, rect will be the
+ // smallest axis aligned bounding box containg the transformed rect.
+ void TransformRect(gfx::Rect* rect) const;
+
+ // Applies the reverse transformation on the rectangle. Returns true if
+ // the transformed rectangle was axis aligned. If it returns false,
+ // rect will be the smallest axis aligned bounding box containg the
+ // transformed rect.
bool TransformRectReverse(gfx::Rect* rect) const;
// Returns the underlying matrix.
- const SkMatrix& matrix() const { return matrix_; }
- SkMatrix& matrix() { return matrix_; }
+ const SkMatrix44& matrix() const { return matrix_; }
+ SkMatrix44& matrix() { return matrix_; }
private:
- SkMatrix matrix_;
+ void TransformPointInternal(const SkMatrix44& xform,
+ gfx::Point& point) const;
+
+ void TransformPointInternal(const SkMatrix44& xform,
+ gfx::Point3f& point) const;
+
+ SkMatrix44 matrix_;
// copy/assign are allowed.
};
-} // namespace ui
+}// namespace ui
#endif // UI_GFX_TRANSFORM_H_
diff --git a/ui/gfx/transform_unittest.cc b/ui/gfx/transform_unittest.cc
new file mode 100644
index 0000000..c536237
--- /dev/null
+++ b/ui/gfx/transform_unittest.cc
@@ -0,0 +1,515 @@
+// 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/transform.h"
+
+#include <iostream>
+#include <limits>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/point3.h"
+
+namespace {
+
+bool PointsAreNearlyEqual(const gfx::Point3f& lhs,
+ const gfx::Point3f& rhs) {
+ float epsilon = 0.0001f;
+ return lhs.SquaredDistanceTo(rhs) < epsilon;
+}
+
+TEST(XFormTest, Equality) {
+ ui::Transform lhs, rhs, interpolated;
+ rhs.matrix().set3x3(1, 2, 3,
+ 4, 5, 6,
+ 7, 8, 9);
+ interpolated = lhs;
+ for (int i = 0; i <= 100; ++i) {
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ float a = lhs.matrix().get(row, col);
+ float b = rhs.matrix().get(row, col);
+ float t = i / 100.0f;
+ interpolated.matrix().set(row, col, a + (b - a) * t);
+ }
+ }
+ if (i == 100) {
+ EXPECT_TRUE(rhs == interpolated);
+ } else {
+ EXPECT_TRUE(rhs != interpolated);
+ }
+ }
+ lhs = ui::Transform();
+ rhs = ui::Transform();
+ for (int i = 1; i < 100; ++i) {
+ lhs.SetTranslate(i, i);
+ rhs.SetTranslate(-i, -i);
+ EXPECT_TRUE(lhs != rhs);
+ rhs.ConcatTranslate(2*i, 2*i);
+ EXPECT_TRUE(lhs == rhs);
+ }
+}
+
+TEST(XFormTest, ConcatTranslate) {
+ static const struct TestCase {
+ int x1;
+ int y1;
+ float tx;
+ float ty;
+ int x2;
+ int y2;
+ } test_cases[] = {
+ { 0, 0, 10.0f, 20.0f, 10, 20 },
+ { 0, 0, -10.0f, -20.0f, 0, 0 },
+ { 0, 0, -10.0f, -20.0f, -10, -20 },
+ { 0, 0,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 10, 20 },
+ };
+
+ ui::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);
+ xform.TransformPoint(p1);
+ if (value.tx == value.tx &&
+ value.ty == value.ty) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ }
+ }
+}
+
+TEST(XFormTest, ConcatScale) {
+ static const struct TestCase {
+ int before;
+ float scale;
+ int after;
+ } test_cases[] = {
+ { 1, 10.0f, 10 },
+ { 1, .1f, 1 },
+ { 1, 100.0f, 100 },
+ { 1, -1.0f, -100 },
+ { 1, std::numeric_limits<float>::quiet_NaN(), 1 }
+ };
+
+ ui::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);
+ xform.TransformPoint(p1);
+ if (value.scale == value.scale) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ }
+ }
+}
+
+TEST(XFormTest, ConcatRotate) {
+ static const struct TestCase {
+ int x1;
+ int y1;
+ float degrees;
+ int x2;
+ int y2;
+ } test_cases[] = {
+ { 1, 0, 90.0f, 0, 1 },
+ { 1, 0, -90.0f, 1, 0 },
+ { 1, 0, 90.0f, 0, 1 },
+ { 1, 0, 360.0f, 0, 1 },
+ { 1, 0, 0.0f, 0, 1 },
+ { 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0 }
+ };
+
+ ui::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);
+ xform.TransformPoint(p1);
+ if (value.degrees == value.degrees) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ }
+ }
+}
+
+TEST(XFormTest, SetTranslate) {
+ static const struct TestCase {
+ int x1; int y1;
+ float tx; float ty;
+ int x2; int y2;
+ } test_cases[] = {
+ { 0, 0, 10.0f, 20.0f, 10, 20 },
+ { 10, 20, 10.0f, 20.0f, 20, 40 },
+ { 10, 20, 0.0f, 0.0f, 10, 20 },
+ { 0, 0,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 0, 0 }
+ };
+
+ 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;
+ ui::Transform xform;
+ switch (k) {
+ case 0:
+ p1.SetPoint(value.x1, 0, 0);
+ p2.SetPoint(value.x2, 0, 0);
+ xform.SetTranslateX(value.tx);
+ break;
+ case 1:
+ p1.SetPoint(0, value.y1, 0);
+ p2.SetPoint(0, value.y2, 0);
+ xform.SetTranslateY(value.ty);
+ break;
+ case 2:
+ p1.SetPoint(value.x1, value.y1, 0);
+ p2.SetPoint(value.x2, value.y2, 0);
+ xform.SetTranslate(value.tx, value.ty);
+ break;
+ }
+ p0 = p1;
+ xform.TransformPoint(p1);
+ if (value.tx == value.tx &&
+ value.ty == value.ty) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ xform.TransformPointReverse(p1);
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
+ }
+ }
+ }
+}
+
+TEST(XFormTest, SetScale) {
+ static const struct TestCase {
+ int before;
+ float s;
+ int after;
+ } test_cases[] = {
+ { 1, 10.0f, 10 },
+ { 1, 1.0f, 1 },
+ { 1, 0.0f, 0 },
+ { 0, 10.0f, 0 },
+ { 1, std::numeric_limits<float>::quiet_NaN(), 0 },
+ };
+
+ 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;
+ ui::Transform xform;
+ switch (k) {
+ case 0:
+ p1.SetPoint(value.before, 0, 0);
+ p2.SetPoint(value.after, 0, 0);
+ xform.SetScaleX(value.s);
+ break;
+ case 1:
+ p1.SetPoint(0, value.before, 0);
+ p2.SetPoint(0, value.after, 0);
+ xform.SetScaleY(value.s);
+ break;
+ case 2:
+ p1.SetPoint(value.before, value.before, 0);
+ p2.SetPoint(value.after, value.after, 0);
+ xform.SetScale(value.s, value.s);
+ break;
+ }
+ p0 = p1;
+ xform.TransformPoint(p1);
+ if (value.s == value.s) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ if (value.s != 0.0f) {
+ xform.TransformPointReverse(p1);
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
+ }
+ }
+ }
+ }
+}
+
+TEST(XFormTest, SetRotate) {
+ static const struct SetRotateCase {
+ int x;
+ int y;
+ float degree;
+ int xprime;
+ int yprime;
+ } set_rotate_cases[] = {
+ { 100, 0, 90.0f, 0, 100 },
+ { 0, 0, 90.0f, 0, 0 },
+ { 0, 100, 90.0f, -100, 0 },
+ { 0, 1, -90.0f, 1, 0 },
+ { 100, 0, 0.0f, 100, 0 },
+ { 0, 0, 0.0f, 0, 0 },
+ { 0, 0, std::numeric_limits<float>::quiet_NaN(), 0, 0 },
+ { 100, 0, 360.0f, 100, 0 }
+ };
+
+ 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);
+ p0 = p1;
+ ui::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) {
+ xform.TransformPoint(p1);
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ xform.TransformPointReverse(p1);
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
+ }
+ }
+}
+
+// 2D tests
+TEST(XFormTest, ConcatTranslate2D) {
+ static const struct TestCase {
+ int x1;
+ int y1;
+ float tx;
+ float ty;
+ int x2;
+ int y2;
+ } test_cases[] = {
+ { 0, 0, 10.0f, 20.0f, 10, 20},
+ { 0, 0, -10.0f, -20.0f, 0, 0},
+ { 0, 0, -10.0f, -20.0f, -10, -20},
+ { 0, 0,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 10, 20},
+ };
+
+ ui::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);
+ xform.TransformPoint(p1);
+ if (value.tx == value.tx &&
+ value.ty == value.ty) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ }
+ }
+}
+
+TEST(XFormTest, ConcatScale2D) {
+ static const struct TestCase {
+ int before;
+ float scale;
+ int after;
+ } test_cases[] = {
+ { 1, 10.0f, 10},
+ { 1, .1f, 1},
+ { 1, 100.0f, 100},
+ { 1, -1.0f, -100},
+ { 1, std::numeric_limits<float>::quiet_NaN(), 1}
+ };
+
+ ui::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);
+ xform.TransformPoint(p1);
+ if (value.scale == value.scale) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ }
+ }
+}
+
+TEST(XFormTest, ConcatRotate2D) {
+ static const struct TestCase {
+ int x1;
+ int y1;
+ float degrees;
+ int x2;
+ int y2;
+ } test_cases[] = {
+ { 1, 0, 90.0f, 0, 1},
+ { 1, 0, -90.0f, 1, 0},
+ { 1, 0, 90.0f, 0, 1},
+ { 1, 0, 360.0f, 0, 1},
+ { 1, 0, 0.0f, 0, 1},
+ { 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0}
+ };
+
+ ui::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);
+ xform.TransformPoint(p1);
+ if (value.degrees == value.degrees) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ }
+ }
+}
+
+TEST(XFormTest, SetTranslate2D) {
+ static const struct TestCase {
+ int x1; int y1;
+ float tx; float ty;
+ int x2; int y2;
+ } test_cases[] = {
+ { 0, 0, 10.0f, 20.0f, 10, 20},
+ { 10, 20, 10.0f, 20.0f, 20, 40},
+ { 10, 20, 0.0f, 0.0f, 10, 20},
+ { 0, 0,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 0, 0}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ for (int j = -1; j < 2; ++j) {
+ for (int k = 0; k < 3; ++k) {
+ float epsilon = 0.0001f;
+ gfx::Point p0, p1, p2;
+ ui::Transform xform;
+ switch (k) {
+ case 0:
+ p1.SetPoint(value.x1, 0);
+ p2.SetPoint(value.x2, 0);
+ xform.SetTranslateX(value.tx + j * epsilon);
+ break;
+ case 1:
+ p1.SetPoint(0, value.y1);
+ p2.SetPoint(0, value.y2);
+ xform.SetTranslateY(value.ty + j * epsilon);
+ break;
+ case 2:
+ p1.SetPoint(value.x1, value.y1);
+ p2.SetPoint(value.x2, value.y2);
+ xform.SetTranslate(value.tx + j * epsilon,
+ value.ty + j * epsilon);
+ break;
+ }
+ p0 = p1;
+ xform.TransformPoint(p1);
+ if (value.tx == value.tx &&
+ value.ty == value.ty) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ xform.TransformPointReverse(p1);
+ EXPECT_EQ(p1.x(), p0.x());
+ EXPECT_EQ(p1.y(), p0.y());
+ }
+ }
+ }
+ }
+}
+
+TEST(XFormTest, SetScale2D) {
+ static const struct TestCase {
+ int before;
+ float s;
+ int after;
+ } test_cases[] = {
+ { 1, 10.0f, 10},
+ { 1, 1.0f, 1},
+ { 1, 0.0f, 0},
+ { 0, 10.0f, 0},
+ { 1, std::numeric_limits<float>::quiet_NaN(), 0},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ for (int j = -1; j < 2; ++j) {
+ for (int k = 0; k < 3; ++k) {
+ float epsilon = 0.0001f;
+ gfx::Point p0, p1, p2;
+ ui::Transform xform;
+ switch (k) {
+ case 0:
+ p1.SetPoint(value.before, 0);
+ p2.SetPoint(value.after, 0);
+ xform.SetScaleX(value.s + j * epsilon);
+ break;
+ case 1:
+ p1.SetPoint(0, value.before);
+ p2.SetPoint(0, value.after);
+ xform.SetScaleY(value.s + j * epsilon);
+ break;
+ case 2:
+ p1.SetPoint(value.before,
+ value.before);
+ p2.SetPoint(value.after,
+ value.after);
+ xform.SetScale(value.s + j * epsilon,
+ value.s + j * epsilon);
+ break;
+ }
+ p0 = p1;
+ xform.TransformPoint(p1);
+ if (value.s == value.s) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ if (value.s != 0.0f) {
+ xform.TransformPointReverse(p1);
+ EXPECT_EQ(p1.x(), p0.x());
+ EXPECT_EQ(p1.y(), p0.y());
+ }
+ }
+ }
+ }
+ }
+}
+
+TEST(XFormTest, SetRotate2D) {
+ static const struct SetRotateCase {
+ int x;
+ int y;
+ float degree;
+ int xprime;
+ int yprime;
+ } set_rotate_cases[] = {
+ { 100, 0, 90.0f, 0, 100},
+ { 0, 0, 90.0f, 0, 0},
+ { 0, 100, 90.0f, -100, 0},
+ { 0, 1, -90.0f, 1, 0},
+ { 100, 0, 0.0f, 100, 0},
+ { 0, 0, 0.0f, 0, 0},
+ { 0, 0, std::numeric_limits<float>::quiet_NaN(), 0, 0},
+ { 100, 0, 360.0f, 100, 0}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(set_rotate_cases); ++i) {
+ 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);
+ ui::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.
+ if (value.degree == value.degree) {
+ xform.TransformPoint(pt);
+ EXPECT_EQ(value.xprime, pt.x());
+ EXPECT_EQ(value.yprime, pt.y());
+ xform.TransformPointReverse(pt);
+ EXPECT_EQ(pt.x(), value.x);
+ EXPECT_EQ(pt.y(), value.y);
+ }
+ }
+ }
+}
+
+} // namespace