diff options
author | backer@chromium.org <backer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-08 15:31:11 +0000 |
---|---|---|
committer | backer@chromium.org <backer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-08 15:31:11 +0000 |
commit | 80248e3395ac8f5d52e8e290435cdce6482a6726 (patch) | |
tree | 397cbe07f35593f6c77c539dbddca331bf7439a5 /ui/gfx | |
parent | a9f090b9d48e668ec4e155b247b2d7fe25fe0f01 (diff) | |
download | chromium_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.h | 2 | ||||
-rw-r--r-- | ui/gfx/canvas_skia.cc | 2 | ||||
-rw-r--r-- | ui/gfx/canvas_skia.h | 2 | ||||
-rw-r--r-- | ui/gfx/compositor/compositor_gl.cc | 25 | ||||
-rw-r--r-- | ui/gfx/compositor/compositor_win.cc | 25 | ||||
-rw-r--r-- | ui/gfx/compositor/layer_animator.cc | 50 | ||||
-rw-r--r-- | ui/gfx/compositor/layer_animator.h | 5 | ||||
-rw-r--r-- | ui/gfx/interpolated_transform_unittest.cc | 18 | ||||
-rw-r--r-- | ui/gfx/point3.h | 63 | ||||
-rw-r--r-- | ui/gfx/transform.cc | 169 | ||||
-rw-r--r-- | ui/gfx/transform.h | 70 | ||||
-rw-r--r-- | ui/gfx/transform_unittest.cc | 515 |
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 |