|  | // Copyright (c) 2012 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. | 
|  |  | 
|  | // 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 "base/logging.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "ui/gfx/geometry/box_f.h" | 
|  | #include "ui/gfx/geometry/point.h" | 
|  | #include "ui/gfx/geometry/point3_f.h" | 
|  | #include "ui/gfx/geometry/rect.h" | 
|  | #include "ui/gfx/geometry/safe_integer_conversions.h" | 
|  | #include "ui/gfx/geometry/vector3d_f.h" | 
|  | #include "ui/gfx/skia_util.h" | 
|  | #include "ui/gfx/transform_util.h" | 
|  |  | 
|  | namespace gfx { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Taken from SkMatrix44. | 
|  | const SkMScalar kEpsilon = 1e-8f; | 
|  |  | 
|  | SkMScalar TanDegrees(double degrees) { | 
|  | double radians = degrees * M_PI / 180; | 
|  | return SkDoubleToMScalar(std::tan(radians)); | 
|  | } | 
|  |  | 
|  | inline bool ApproximatelyZero(SkMScalar x, SkMScalar tolerance) { | 
|  | return std::abs(x) <= tolerance; | 
|  | } | 
|  |  | 
|  | inline bool ApproximatelyOne(SkMScalar x, SkMScalar tolerance) { | 
|  | return std::abs(x - SkDoubleToMScalar(1.0)) <= tolerance; | 
|  | } | 
|  |  | 
|  | static float Round(float f) { | 
|  | if (f == 0.f) | 
|  | return f; | 
|  | return (f > 0.f) ? std::floor(f + 0.5f) : std::ceil(f - 0.5f); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | Transform::Transform(SkMScalar col1row1, | 
|  | SkMScalar col2row1, | 
|  | SkMScalar col3row1, | 
|  | SkMScalar col4row1, | 
|  | SkMScalar col1row2, | 
|  | SkMScalar col2row2, | 
|  | SkMScalar col3row2, | 
|  | SkMScalar col4row2, | 
|  | SkMScalar col1row3, | 
|  | SkMScalar col2row3, | 
|  | SkMScalar col3row3, | 
|  | SkMScalar col4row3, | 
|  | SkMScalar col1row4, | 
|  | SkMScalar col2row4, | 
|  | SkMScalar col3row4, | 
|  | SkMScalar col4row4) | 
|  | : matrix_(SkMatrix44::kUninitialized_Constructor) { | 
|  | matrix_.set(0, 0, col1row1); | 
|  | matrix_.set(1, 0, col1row2); | 
|  | matrix_.set(2, 0, col1row3); | 
|  | matrix_.set(3, 0, col1row4); | 
|  |  | 
|  | matrix_.set(0, 1, col2row1); | 
|  | matrix_.set(1, 1, col2row2); | 
|  | matrix_.set(2, 1, col2row3); | 
|  | matrix_.set(3, 1, col2row4); | 
|  |  | 
|  | matrix_.set(0, 2, col3row1); | 
|  | matrix_.set(1, 2, col3row2); | 
|  | matrix_.set(2, 2, col3row3); | 
|  | matrix_.set(3, 2, col3row4); | 
|  |  | 
|  | matrix_.set(0, 3, col4row1); | 
|  | matrix_.set(1, 3, col4row2); | 
|  | matrix_.set(2, 3, col4row3); | 
|  | matrix_.set(3, 3, col4row4); | 
|  | } | 
|  |  | 
|  | Transform::Transform(SkMScalar col1row1, | 
|  | SkMScalar col2row1, | 
|  | SkMScalar col1row2, | 
|  | SkMScalar col2row2, | 
|  | SkMScalar x_translation, | 
|  | SkMScalar y_translation) | 
|  | : matrix_(SkMatrix44::kIdentity_Constructor) { | 
|  | matrix_.set(0, 0, col1row1); | 
|  | matrix_.set(1, 0, col1row2); | 
|  | matrix_.set(0, 1, col2row1); | 
|  | matrix_.set(1, 1, col2row2); | 
|  | matrix_.set(0, 3, x_translation); | 
|  | matrix_.set(1, 3, y_translation); | 
|  | } | 
|  |  | 
|  | void Transform::RotateAboutXAxis(double degrees) { | 
|  | double radians = degrees * M_PI / 180; | 
|  | SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians)); | 
|  | SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians)); | 
|  | if (matrix_.isIdentity()) { | 
|  | matrix_.set3x3(1, 0, 0, | 
|  | 0, cosTheta, sinTheta, | 
|  | 0, -sinTheta, cosTheta); | 
|  | } else { | 
|  | SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor); | 
|  | rot.set3x3(1, 0, 0, | 
|  | 0, cosTheta, sinTheta, | 
|  | 0, -sinTheta, cosTheta); | 
|  | matrix_.preConcat(rot); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Transform::RotateAboutYAxis(double degrees) { | 
|  | double radians = degrees * M_PI / 180; | 
|  | SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians)); | 
|  | SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians)); | 
|  | if (matrix_.isIdentity()) { | 
|  | // Note carefully the placement of the -sinTheta for rotation about | 
|  | // y-axis is different than rotation about x-axis or z-axis. | 
|  | matrix_.set3x3(cosTheta, 0, -sinTheta, | 
|  | 0, 1, 0, | 
|  | sinTheta, 0, cosTheta); | 
|  | } else { | 
|  | SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor); | 
|  | rot.set3x3(cosTheta, 0, -sinTheta, | 
|  | 0, 1, 0, | 
|  | sinTheta, 0, cosTheta); | 
|  | matrix_.preConcat(rot); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Transform::RotateAboutZAxis(double degrees) { | 
|  | double radians = degrees * M_PI / 180; | 
|  | SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians)); | 
|  | SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians)); | 
|  | if (matrix_.isIdentity()) { | 
|  | matrix_.set3x3(cosTheta, sinTheta, 0, | 
|  | -sinTheta, cosTheta, 0, | 
|  | 0, 0, 1); | 
|  | } else { | 
|  | SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor); | 
|  | rot.set3x3(cosTheta, sinTheta, 0, | 
|  | -sinTheta, cosTheta, 0, | 
|  | 0, 0, 1); | 
|  | matrix_.preConcat(rot); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Transform::RotateAbout(const Vector3dF& axis, double degrees) { | 
|  | if (matrix_.isIdentity()) { | 
|  | matrix_.setRotateDegreesAbout(SkFloatToMScalar(axis.x()), | 
|  | SkFloatToMScalar(axis.y()), | 
|  | SkFloatToMScalar(axis.z()), | 
|  | degrees); | 
|  | } else { | 
|  | SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor); | 
|  | rot.setRotateDegreesAbout(SkFloatToMScalar(axis.x()), | 
|  | SkFloatToMScalar(axis.y()), | 
|  | SkFloatToMScalar(axis.z()), | 
|  | degrees); | 
|  | matrix_.preConcat(rot); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Transform::Scale(SkMScalar x, SkMScalar y) { matrix_.preScale(x, y, 1); } | 
|  |  | 
|  | void Transform::Scale3d(SkMScalar x, SkMScalar y, SkMScalar z) { | 
|  | matrix_.preScale(x, y, z); | 
|  | } | 
|  |  | 
|  | void Transform::Translate(SkMScalar x, SkMScalar y) { | 
|  | matrix_.preTranslate(x, y, 0); | 
|  | } | 
|  |  | 
|  | void Transform::Translate3d(SkMScalar x, SkMScalar y, SkMScalar z) { | 
|  | matrix_.preTranslate(x, y, z); | 
|  | } | 
|  |  | 
|  | void Transform::SkewX(double angle_x) { | 
|  | if (matrix_.isIdentity()) | 
|  | matrix_.set(0, 1, TanDegrees(angle_x)); | 
|  | else { | 
|  | SkMatrix44 skew(SkMatrix44::kIdentity_Constructor); | 
|  | skew.set(0, 1, TanDegrees(angle_x)); | 
|  | matrix_.preConcat(skew); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Transform::SkewY(double angle_y) { | 
|  | if (matrix_.isIdentity()) | 
|  | matrix_.set(1, 0, TanDegrees(angle_y)); | 
|  | else { | 
|  | SkMatrix44 skew(SkMatrix44::kIdentity_Constructor); | 
|  | skew.set(1, 0, TanDegrees(angle_y)); | 
|  | matrix_.preConcat(skew); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Transform::ApplyPerspectiveDepth(SkMScalar depth) { | 
|  | if (depth == 0) | 
|  | return; | 
|  | if (matrix_.isIdentity()) | 
|  | matrix_.set(3, 2, -1.0 / depth); | 
|  | else { | 
|  | SkMatrix44 m(SkMatrix44::kIdentity_Constructor); | 
|  | m.set(3, 2, -1.0 / depth); | 
|  | matrix_.preConcat(m); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Transform::PreconcatTransform(const Transform& transform) { | 
|  | matrix_.preConcat(transform.matrix_); | 
|  | } | 
|  |  | 
|  | void Transform::ConcatTransform(const Transform& transform) { | 
|  | matrix_.postConcat(transform.matrix_); | 
|  | } | 
|  |  | 
|  | bool Transform::IsApproximatelyIdentityOrTranslation( | 
|  | SkMScalar tolerance) const { | 
|  | DCHECK_GE(tolerance, 0); | 
|  | return | 
|  | ApproximatelyOne(matrix_.get(0, 0), tolerance) && | 
|  | ApproximatelyZero(matrix_.get(1, 0), tolerance) && | 
|  | ApproximatelyZero(matrix_.get(2, 0), tolerance) && | 
|  | matrix_.get(3, 0) == 0 && | 
|  | ApproximatelyZero(matrix_.get(0, 1), tolerance) && | 
|  | ApproximatelyOne(matrix_.get(1, 1), tolerance) && | 
|  | ApproximatelyZero(matrix_.get(2, 1), tolerance) && | 
|  | matrix_.get(3, 1) == 0 && | 
|  | ApproximatelyZero(matrix_.get(0, 2), tolerance) && | 
|  | ApproximatelyZero(matrix_.get(1, 2), tolerance) && | 
|  | ApproximatelyOne(matrix_.get(2, 2), tolerance) && | 
|  | matrix_.get(3, 2) == 0 && | 
|  | matrix_.get(3, 3) == 1; | 
|  | } | 
|  |  | 
|  | bool Transform::IsIdentityOrIntegerTranslation() const { | 
|  | if (!IsIdentityOrTranslation()) | 
|  | return false; | 
|  |  | 
|  | bool no_fractional_translation = | 
|  | static_cast<int>(matrix_.get(0, 3)) == matrix_.get(0, 3) && | 
|  | static_cast<int>(matrix_.get(1, 3)) == matrix_.get(1, 3) && | 
|  | static_cast<int>(matrix_.get(2, 3)) == matrix_.get(2, 3); | 
|  |  | 
|  | return no_fractional_translation; | 
|  | } | 
|  |  | 
|  | bool Transform::IsBackFaceVisible() const { | 
|  | // Compute whether a layer with a forward-facing normal of (0, 0, 1, 0) | 
|  | // would have its back face visible after applying the transform. | 
|  | if (matrix_.isIdentity()) | 
|  | return false; | 
|  |  | 
|  | // This is done by transforming the normal and seeing if the resulting z | 
|  | // value is positive or negative. However, note that transforming a normal | 
|  | // actually requires using the inverse-transpose of the original transform. | 
|  | // | 
|  | // We can avoid inverting and transposing the matrix since we know we want | 
|  | // to transform only the specific normal vector (0, 0, 1, 0). In this case, | 
|  | // we only need the 3rd row, 3rd column of the inverse-transpose. We can | 
|  | // calculate only the 3rd row 3rd column element of the inverse, skipping | 
|  | // everything else. | 
|  | // | 
|  | // For more information, refer to: | 
|  | //   http://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution | 
|  | // | 
|  |  | 
|  | double determinant = matrix_.determinant(); | 
|  |  | 
|  | // If matrix was not invertible, then just assume back face is not visible. | 
|  | if (std::abs(determinant) <= kEpsilon) | 
|  | return false; | 
|  |  | 
|  | // Compute the cofactor of the 3rd row, 3rd column. | 
|  | double cofactor_part_1 = | 
|  | matrix_.get(0, 0) * matrix_.get(1, 1) * matrix_.get(3, 3); | 
|  |  | 
|  | double cofactor_part_2 = | 
|  | matrix_.get(0, 1) * matrix_.get(1, 3) * matrix_.get(3, 0); | 
|  |  | 
|  | double cofactor_part_3 = | 
|  | matrix_.get(0, 3) * matrix_.get(1, 0) * matrix_.get(3, 1); | 
|  |  | 
|  | double cofactor_part_4 = | 
|  | matrix_.get(0, 0) * matrix_.get(1, 3) * matrix_.get(3, 1); | 
|  |  | 
|  | double cofactor_part_5 = | 
|  | matrix_.get(0, 1) * matrix_.get(1, 0) * matrix_.get(3, 3); | 
|  |  | 
|  | double cofactor_part_6 = | 
|  | matrix_.get(0, 3) * matrix_.get(1, 1) * matrix_.get(3, 0); | 
|  |  | 
|  | double cofactor33 = | 
|  | cofactor_part_1 + | 
|  | cofactor_part_2 + | 
|  | cofactor_part_3 - | 
|  | cofactor_part_4 - | 
|  | cofactor_part_5 - | 
|  | cofactor_part_6; | 
|  |  | 
|  | // Technically the transformed z component is cofactor33 / determinant.  But | 
|  | // we can avoid the costly division because we only care about the resulting | 
|  | // +/- sign; we can check this equivalently by multiplication. | 
|  | return cofactor33 * determinant < 0; | 
|  | } | 
|  |  | 
|  | bool Transform::GetInverse(Transform* transform) const { | 
|  | if (!matrix_.invert(&transform->matrix_)) { | 
|  | // Initialize the return value to identity if this matrix turned | 
|  | // out to be un-invertible. | 
|  | transform->MakeIdentity(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Transform::Preserves2dAxisAlignment() const { | 
|  | // Check whether an axis aligned 2-dimensional rect would remain axis-aligned | 
|  | // after being transformed by this matrix (and implicitly projected by | 
|  | // dropping any non-zero z-values). | 
|  | // | 
|  | // The 4th column can be ignored because translations don't affect axis | 
|  | // alignment. The 3rd column can be ignored because we are assuming 2d | 
|  | // inputs, where z-values will be zero. The 3rd row can also be ignored | 
|  | // because we are assuming 2d outputs, and any resulting z-value is dropped | 
|  | // anyway. For the inner 2x2 portion, the only effects that keep a rect axis | 
|  | // aligned are (1) swapping axes and (2) scaling axes. This can be checked by | 
|  | // verifying only 1 element of every column and row is non-zero.  Degenerate | 
|  | // cases that project the x or y dimension to zero are considered to preserve | 
|  | // axis alignment. | 
|  | // | 
|  | // If the matrix does have perspective component that is affected by x or y | 
|  | // values: The current implementation conservatively assumes that axis | 
|  | // alignment is not preserved. | 
|  |  | 
|  | bool has_x_or_y_perspective = | 
|  | matrix_.get(3, 0) != 0 || matrix_.get(3, 1) != 0; | 
|  |  | 
|  | int num_non_zero_in_row_0 = 0; | 
|  | int num_non_zero_in_row_1 = 0; | 
|  | int num_non_zero_in_col_0 = 0; | 
|  | int num_non_zero_in_col_1 = 0; | 
|  |  | 
|  | if (std::abs(matrix_.get(0, 0)) > kEpsilon) { | 
|  | num_non_zero_in_row_0++; | 
|  | num_non_zero_in_col_0++; | 
|  | } | 
|  |  | 
|  | if (std::abs(matrix_.get(0, 1)) > kEpsilon) { | 
|  | num_non_zero_in_row_0++; | 
|  | num_non_zero_in_col_1++; | 
|  | } | 
|  |  | 
|  | if (std::abs(matrix_.get(1, 0)) > kEpsilon) { | 
|  | num_non_zero_in_row_1++; | 
|  | num_non_zero_in_col_0++; | 
|  | } | 
|  |  | 
|  | if (std::abs(matrix_.get(1, 1)) > kEpsilon) { | 
|  | num_non_zero_in_row_1++; | 
|  | num_non_zero_in_col_1++; | 
|  | } | 
|  |  | 
|  | return | 
|  | num_non_zero_in_row_0 <= 1 && | 
|  | num_non_zero_in_row_1 <= 1 && | 
|  | num_non_zero_in_col_0 <= 1 && | 
|  | num_non_zero_in_col_1 <= 1 && | 
|  | !has_x_or_y_perspective; | 
|  | } | 
|  |  | 
|  | void Transform::Transpose() { | 
|  | matrix_.transpose(); | 
|  | } | 
|  |  | 
|  | void Transform::FlattenTo2d() { | 
|  | matrix_.set(2, 0, 0.0); | 
|  | matrix_.set(2, 1, 0.0); | 
|  | matrix_.set(0, 2, 0.0); | 
|  | matrix_.set(1, 2, 0.0); | 
|  | matrix_.set(2, 2, 1.0); | 
|  | matrix_.set(3, 2, 0.0); | 
|  | matrix_.set(2, 3, 0.0); | 
|  | } | 
|  |  | 
|  | bool Transform::IsFlat() const { | 
|  | return matrix_.get(2, 0) == 0.0 && matrix_.get(2, 1) == 0.0 && | 
|  | matrix_.get(0, 2) == 0.0 && matrix_.get(1, 2) == 0.0 && | 
|  | matrix_.get(2, 2) == 1.0 && matrix_.get(3, 2) == 0.0 && | 
|  | matrix_.get(2, 3) == 0.0; | 
|  | } | 
|  |  | 
|  | Vector2dF Transform::To2dTranslation() const { | 
|  | return gfx::Vector2dF(SkMScalarToFloat(matrix_.get(0, 3)), | 
|  | SkMScalarToFloat(matrix_.get(1, 3))); | 
|  | } | 
|  |  | 
|  | void Transform::TransformPoint(Point* point) const { | 
|  | DCHECK(point); | 
|  | TransformPointInternal(matrix_, point); | 
|  | } | 
|  |  | 
|  | void Transform::TransformPoint(Point3F* point) const { | 
|  | DCHECK(point); | 
|  | TransformPointInternal(matrix_, point); | 
|  | } | 
|  |  | 
|  | bool Transform::TransformPointReverse(Point* point) const { | 
|  | DCHECK(point); | 
|  |  | 
|  | // TODO(sad): Try to avoid trying to invert the matrix. | 
|  | SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor); | 
|  | if (!matrix_.invert(&inverse)) | 
|  | return false; | 
|  |  | 
|  | TransformPointInternal(inverse, point); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Transform::TransformPointReverse(Point3F* point) const { | 
|  | DCHECK(point); | 
|  |  | 
|  | // TODO(sad): Try to avoid trying to invert the matrix. | 
|  | SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor); | 
|  | if (!matrix_.invert(&inverse)) | 
|  | return false; | 
|  |  | 
|  | TransformPointInternal(inverse, point); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Transform::TransformRect(RectF* rect) const { | 
|  | if (matrix_.isIdentity()) | 
|  | return; | 
|  |  | 
|  | SkRect src = RectFToSkRect(*rect); | 
|  | const SkMatrix& matrix = matrix_; | 
|  | matrix.mapRect(&src); | 
|  | *rect = SkRectToRectF(src); | 
|  | } | 
|  |  | 
|  | bool Transform::TransformRectReverse(RectF* rect) const { | 
|  | if (matrix_.isIdentity()) | 
|  | return true; | 
|  |  | 
|  | SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor); | 
|  | if (!matrix_.invert(&inverse)) | 
|  | return false; | 
|  |  | 
|  | const SkMatrix& matrix = inverse; | 
|  | SkRect src = RectFToSkRect(*rect); | 
|  | matrix.mapRect(&src); | 
|  | *rect = SkRectToRectF(src); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Transform::TransformBox(BoxF* box) const { | 
|  | BoxF bounds; | 
|  | bool first_point = true; | 
|  | for (int corner = 0; corner < 8; ++corner) { | 
|  | gfx::Point3F point = box->origin(); | 
|  | point += gfx::Vector3dF(corner & 1 ? box->width() : 0.f, | 
|  | corner & 2 ? box->height() : 0.f, | 
|  | corner & 4 ? box->depth() : 0.f); | 
|  | TransformPoint(&point); | 
|  | if (first_point) { | 
|  | bounds.set_origin(point); | 
|  | first_point = false; | 
|  | } else { | 
|  | bounds.ExpandTo(point); | 
|  | } | 
|  | } | 
|  | *box = bounds; | 
|  | } | 
|  |  | 
|  | bool Transform::TransformBoxReverse(BoxF* box) const { | 
|  | gfx::Transform inverse = *this; | 
|  | if (!GetInverse(&inverse)) | 
|  | return false; | 
|  | inverse.TransformBox(box); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Transform::Blend(const Transform& from, double progress) { | 
|  | 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::RoundTranslationComponents() { | 
|  | matrix_.set(0, 3, Round(matrix_.get(0, 3))); | 
|  | matrix_.set(1, 3, Round(matrix_.get(1, 3))); | 
|  | } | 
|  |  | 
|  |  | 
|  | void Transform::TransformPointInternal(const SkMatrix44& xform, | 
|  | Point3F* point) const { | 
|  | if (xform.isIdentity()) | 
|  | return; | 
|  |  | 
|  | SkMScalar p[4] = {SkFloatToMScalar(point->x()), SkFloatToMScalar(point->y()), | 
|  | SkFloatToMScalar(point->z()), 1}; | 
|  |  | 
|  | xform.mapMScalars(p); | 
|  |  | 
|  | if (p[3] != SK_MScalar1 && p[3] != 0.f) { | 
|  | float w_inverse = SK_MScalar1 / p[3]; | 
|  | point->SetPoint(p[0] * w_inverse, p[1] * w_inverse, p[2] * w_inverse); | 
|  | } else { | 
|  | point->SetPoint(p[0], p[1], p[2]); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Transform::TransformPointInternal(const SkMatrix44& xform, | 
|  | Point* point) const { | 
|  | if (xform.isIdentity()) | 
|  | return; | 
|  |  | 
|  | SkMScalar p[4] = {SkFloatToMScalar(point->x()), SkFloatToMScalar(point->y()), | 
|  | 0, 1}; | 
|  |  | 
|  | xform.mapMScalars(p); | 
|  |  | 
|  | point->SetPoint(ToRoundedInt(p[0]), ToRoundedInt(p[1])); | 
|  | } | 
|  |  | 
|  | std::string Transform::ToString() const { | 
|  | return base::StringPrintf( | 
|  | "[ %+0.4f %+0.4f %+0.4f %+0.4f  \n" | 
|  | "  %+0.4f %+0.4f %+0.4f %+0.4f  \n" | 
|  | "  %+0.4f %+0.4f %+0.4f %+0.4f  \n" | 
|  | "  %+0.4f %+0.4f %+0.4f %+0.4f ]\n", | 
|  | matrix_.get(0, 0), | 
|  | matrix_.get(0, 1), | 
|  | matrix_.get(0, 2), | 
|  | matrix_.get(0, 3), | 
|  | matrix_.get(1, 0), | 
|  | matrix_.get(1, 1), | 
|  | matrix_.get(1, 2), | 
|  | matrix_.get(1, 3), | 
|  | matrix_.get(2, 0), | 
|  | matrix_.get(2, 1), | 
|  | matrix_.get(2, 2), | 
|  | matrix_.get(2, 3), | 
|  | matrix_.get(3, 0), | 
|  | matrix_.get(3, 1), | 
|  | matrix_.get(3, 2), | 
|  | matrix_.get(3, 3)); | 
|  | } | 
|  |  | 
|  | }  // namespace gfx |