Add a basic settings page with a Polymer paper element which loads at chrome://md...
[chromium-blink-merge.git] / ui / gfx / transform.cc
blob722d2ed144c75df1e40299ecbbc315afa9fdc8e6
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // MSVC++ requires this to be set before any other includes to get M_PI.
6 #define _USE_MATH_DEFINES
8 #include "ui/gfx/transform.h"
10 #include <cmath>
12 #include "base/logging.h"
13 #include "base/strings/stringprintf.h"
14 #include "ui/gfx/geometry/box_f.h"
15 #include "ui/gfx/geometry/point.h"
16 #include "ui/gfx/geometry/point3_f.h"
17 #include "ui/gfx/geometry/rect.h"
18 #include "ui/gfx/geometry/safe_integer_conversions.h"
19 #include "ui/gfx/geometry/vector3d_f.h"
20 #include "ui/gfx/skia_util.h"
21 #include "ui/gfx/transform_util.h"
23 namespace gfx {
25 namespace {
27 // Taken from SkMatrix44.
28 const SkMScalar kEpsilon = 1e-8f;
30 SkMScalar TanDegrees(double degrees) {
31 double radians = degrees * M_PI / 180;
32 return SkDoubleToMScalar(std::tan(radians));
35 inline bool ApproximatelyZero(SkMScalar x, SkMScalar tolerance) {
36 return std::abs(x) <= tolerance;
39 inline bool ApproximatelyOne(SkMScalar x, SkMScalar tolerance) {
40 return std::abs(x - SkDoubleToMScalar(1.0)) <= tolerance;
43 } // namespace
45 Transform::Transform(SkMScalar col1row1,
46 SkMScalar col2row1,
47 SkMScalar col3row1,
48 SkMScalar col4row1,
49 SkMScalar col1row2,
50 SkMScalar col2row2,
51 SkMScalar col3row2,
52 SkMScalar col4row2,
53 SkMScalar col1row3,
54 SkMScalar col2row3,
55 SkMScalar col3row3,
56 SkMScalar col4row3,
57 SkMScalar col1row4,
58 SkMScalar col2row4,
59 SkMScalar col3row4,
60 SkMScalar col4row4)
61 : matrix_(SkMatrix44::kUninitialized_Constructor) {
62 matrix_.set(0, 0, col1row1);
63 matrix_.set(1, 0, col1row2);
64 matrix_.set(2, 0, col1row3);
65 matrix_.set(3, 0, col1row4);
67 matrix_.set(0, 1, col2row1);
68 matrix_.set(1, 1, col2row2);
69 matrix_.set(2, 1, col2row3);
70 matrix_.set(3, 1, col2row4);
72 matrix_.set(0, 2, col3row1);
73 matrix_.set(1, 2, col3row2);
74 matrix_.set(2, 2, col3row3);
75 matrix_.set(3, 2, col3row4);
77 matrix_.set(0, 3, col4row1);
78 matrix_.set(1, 3, col4row2);
79 matrix_.set(2, 3, col4row3);
80 matrix_.set(3, 3, col4row4);
83 Transform::Transform(SkMScalar col1row1,
84 SkMScalar col2row1,
85 SkMScalar col1row2,
86 SkMScalar col2row2,
87 SkMScalar x_translation,
88 SkMScalar y_translation)
89 : matrix_(SkMatrix44::kIdentity_Constructor) {
90 matrix_.set(0, 0, col1row1);
91 matrix_.set(1, 0, col1row2);
92 matrix_.set(0, 1, col2row1);
93 matrix_.set(1, 1, col2row2);
94 matrix_.set(0, 3, x_translation);
95 matrix_.set(1, 3, y_translation);
98 void Transform::RotateAboutXAxis(double degrees) {
99 double radians = degrees * M_PI / 180;
100 SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians));
101 SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians));
102 if (matrix_.isIdentity()) {
103 matrix_.set3x3(1, 0, 0,
104 0, cosTheta, sinTheta,
105 0, -sinTheta, cosTheta);
106 } else {
107 SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
108 rot.set3x3(1, 0, 0,
109 0, cosTheta, sinTheta,
110 0, -sinTheta, cosTheta);
111 matrix_.preConcat(rot);
115 void Transform::RotateAboutYAxis(double degrees) {
116 double radians = degrees * M_PI / 180;
117 SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians));
118 SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians));
119 if (matrix_.isIdentity()) {
120 // Note carefully the placement of the -sinTheta for rotation about
121 // y-axis is different than rotation about x-axis or z-axis.
122 matrix_.set3x3(cosTheta, 0, -sinTheta,
123 0, 1, 0,
124 sinTheta, 0, cosTheta);
125 } else {
126 SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
127 rot.set3x3(cosTheta, 0, -sinTheta,
128 0, 1, 0,
129 sinTheta, 0, cosTheta);
130 matrix_.preConcat(rot);
134 void Transform::RotateAboutZAxis(double degrees) {
135 double radians = degrees * M_PI / 180;
136 SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians));
137 SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians));
138 if (matrix_.isIdentity()) {
139 matrix_.set3x3(cosTheta, sinTheta, 0,
140 -sinTheta, cosTheta, 0,
141 0, 0, 1);
142 } else {
143 SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
144 rot.set3x3(cosTheta, sinTheta, 0,
145 -sinTheta, cosTheta, 0,
146 0, 0, 1);
147 matrix_.preConcat(rot);
151 void Transform::RotateAbout(const Vector3dF& axis, double degrees) {
152 if (matrix_.isIdentity()) {
153 matrix_.setRotateDegreesAbout(SkFloatToMScalar(axis.x()),
154 SkFloatToMScalar(axis.y()),
155 SkFloatToMScalar(axis.z()),
156 SkDoubleToMScalar(degrees));
157 } else {
158 SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
159 rot.setRotateDegreesAbout(SkFloatToMScalar(axis.x()),
160 SkFloatToMScalar(axis.y()),
161 SkFloatToMScalar(axis.z()),
162 SkDoubleToMScalar(degrees));
163 matrix_.preConcat(rot);
167 void Transform::Scale(SkMScalar x, SkMScalar y) { matrix_.preScale(x, y, 1); }
169 void Transform::Scale3d(SkMScalar x, SkMScalar y, SkMScalar z) {
170 matrix_.preScale(x, y, z);
173 void Transform::Translate(SkMScalar x, SkMScalar y) {
174 matrix_.preTranslate(x, y, 0);
177 void Transform::Translate3d(SkMScalar x, SkMScalar y, SkMScalar z) {
178 matrix_.preTranslate(x, y, z);
181 void Transform::SkewX(double angle_x) {
182 if (matrix_.isIdentity()) {
183 matrix_.set(0, 1, TanDegrees(angle_x));
184 } else {
185 SkMatrix44 skew(SkMatrix44::kIdentity_Constructor);
186 skew.set(0, 1, TanDegrees(angle_x));
187 matrix_.preConcat(skew);
191 void Transform::SkewY(double angle_y) {
192 if (matrix_.isIdentity()) {
193 matrix_.set(1, 0, TanDegrees(angle_y));
194 } else {
195 SkMatrix44 skew(SkMatrix44::kIdentity_Constructor);
196 skew.set(1, 0, TanDegrees(angle_y));
197 matrix_.preConcat(skew);
201 void Transform::ApplyPerspectiveDepth(SkMScalar depth) {
202 if (depth == 0)
203 return;
204 if (matrix_.isIdentity()) {
205 matrix_.set(3, 2, -SK_MScalar1 / depth);
206 } else {
207 SkMatrix44 m(SkMatrix44::kIdentity_Constructor);
208 m.set(3, 2, -SK_MScalar1 / depth);
209 matrix_.preConcat(m);
213 void Transform::PreconcatTransform(const Transform& transform) {
214 matrix_.preConcat(transform.matrix_);
217 void Transform::ConcatTransform(const Transform& transform) {
218 matrix_.postConcat(transform.matrix_);
221 bool Transform::IsApproximatelyIdentityOrTranslation(
222 SkMScalar tolerance) const {
223 DCHECK_GE(tolerance, 0);
224 return
225 ApproximatelyOne(matrix_.get(0, 0), tolerance) &&
226 ApproximatelyZero(matrix_.get(1, 0), tolerance) &&
227 ApproximatelyZero(matrix_.get(2, 0), tolerance) &&
228 matrix_.get(3, 0) == 0 &&
229 ApproximatelyZero(matrix_.get(0, 1), tolerance) &&
230 ApproximatelyOne(matrix_.get(1, 1), tolerance) &&
231 ApproximatelyZero(matrix_.get(2, 1), tolerance) &&
232 matrix_.get(3, 1) == 0 &&
233 ApproximatelyZero(matrix_.get(0, 2), tolerance) &&
234 ApproximatelyZero(matrix_.get(1, 2), tolerance) &&
235 ApproximatelyOne(matrix_.get(2, 2), tolerance) &&
236 matrix_.get(3, 2) == 0 &&
237 matrix_.get(3, 3) == 1;
240 bool Transform::IsIdentityOrIntegerTranslation() const {
241 if (!IsIdentityOrTranslation())
242 return false;
244 bool no_fractional_translation =
245 static_cast<int>(matrix_.get(0, 3)) == matrix_.get(0, 3) &&
246 static_cast<int>(matrix_.get(1, 3)) == matrix_.get(1, 3) &&
247 static_cast<int>(matrix_.get(2, 3)) == matrix_.get(2, 3);
249 return no_fractional_translation;
252 bool Transform::IsBackFaceVisible() const {
253 // Compute whether a layer with a forward-facing normal of (0, 0, 1, 0)
254 // would have its back face visible after applying the transform.
255 if (matrix_.isIdentity())
256 return false;
258 // This is done by transforming the normal and seeing if the resulting z
259 // value is positive or negative. However, note that transforming a normal
260 // actually requires using the inverse-transpose of the original transform.
262 // We can avoid inverting and transposing the matrix since we know we want
263 // to transform only the specific normal vector (0, 0, 1, 0). In this case,
264 // we only need the 3rd row, 3rd column of the inverse-transpose. We can
265 // calculate only the 3rd row 3rd column element of the inverse, skipping
266 // everything else.
268 // For more information, refer to:
269 // http://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution
272 double determinant = matrix_.determinant();
274 // If matrix was not invertible, then just assume back face is not visible.
275 if (std::abs(determinant) <= kEpsilon)
276 return false;
278 // Compute the cofactor of the 3rd row, 3rd column.
279 double cofactor_part_1 =
280 matrix_.get(0, 0) * matrix_.get(1, 1) * matrix_.get(3, 3);
282 double cofactor_part_2 =
283 matrix_.get(0, 1) * matrix_.get(1, 3) * matrix_.get(3, 0);
285 double cofactor_part_3 =
286 matrix_.get(0, 3) * matrix_.get(1, 0) * matrix_.get(3, 1);
288 double cofactor_part_4 =
289 matrix_.get(0, 0) * matrix_.get(1, 3) * matrix_.get(3, 1);
291 double cofactor_part_5 =
292 matrix_.get(0, 1) * matrix_.get(1, 0) * matrix_.get(3, 3);
294 double cofactor_part_6 =
295 matrix_.get(0, 3) * matrix_.get(1, 1) * matrix_.get(3, 0);
297 double cofactor33 =
298 cofactor_part_1 +
299 cofactor_part_2 +
300 cofactor_part_3 -
301 cofactor_part_4 -
302 cofactor_part_5 -
303 cofactor_part_6;
305 // Technically the transformed z component is cofactor33 / determinant. But
306 // we can avoid the costly division because we only care about the resulting
307 // +/- sign; we can check this equivalently by multiplication.
308 return cofactor33 * determinant < 0;
311 bool Transform::GetInverse(Transform* transform) const {
312 if (!matrix_.invert(&transform->matrix_)) {
313 // Initialize the return value to identity if this matrix turned
314 // out to be un-invertible.
315 transform->MakeIdentity();
316 return false;
319 return true;
322 bool Transform::Preserves2dAxisAlignment() const {
323 // Check whether an axis aligned 2-dimensional rect would remain axis-aligned
324 // after being transformed by this matrix (and implicitly projected by
325 // dropping any non-zero z-values).
327 // The 4th column can be ignored because translations don't affect axis
328 // alignment. The 3rd column can be ignored because we are assuming 2d
329 // inputs, where z-values will be zero. The 3rd row can also be ignored
330 // because we are assuming 2d outputs, and any resulting z-value is dropped
331 // anyway. For the inner 2x2 portion, the only effects that keep a rect axis
332 // aligned are (1) swapping axes and (2) scaling axes. This can be checked by
333 // verifying only 1 element of every column and row is non-zero. Degenerate
334 // cases that project the x or y dimension to zero are considered to preserve
335 // axis alignment.
337 // If the matrix does have perspective component that is affected by x or y
338 // values: The current implementation conservatively assumes that axis
339 // alignment is not preserved.
341 bool has_x_or_y_perspective =
342 matrix_.get(3, 0) != 0 || matrix_.get(3, 1) != 0;
344 int num_non_zero_in_row_0 = 0;
345 int num_non_zero_in_row_1 = 0;
346 int num_non_zero_in_col_0 = 0;
347 int num_non_zero_in_col_1 = 0;
349 if (std::abs(matrix_.get(0, 0)) > kEpsilon) {
350 num_non_zero_in_row_0++;
351 num_non_zero_in_col_0++;
354 if (std::abs(matrix_.get(0, 1)) > kEpsilon) {
355 num_non_zero_in_row_0++;
356 num_non_zero_in_col_1++;
359 if (std::abs(matrix_.get(1, 0)) > kEpsilon) {
360 num_non_zero_in_row_1++;
361 num_non_zero_in_col_0++;
364 if (std::abs(matrix_.get(1, 1)) > kEpsilon) {
365 num_non_zero_in_row_1++;
366 num_non_zero_in_col_1++;
369 return
370 num_non_zero_in_row_0 <= 1 &&
371 num_non_zero_in_row_1 <= 1 &&
372 num_non_zero_in_col_0 <= 1 &&
373 num_non_zero_in_col_1 <= 1 &&
374 !has_x_or_y_perspective;
377 void Transform::Transpose() {
378 matrix_.transpose();
381 void Transform::FlattenTo2d() {
382 matrix_.set(2, 0, 0.0);
383 matrix_.set(2, 1, 0.0);
384 matrix_.set(0, 2, 0.0);
385 matrix_.set(1, 2, 0.0);
386 matrix_.set(2, 2, 1.0);
387 matrix_.set(3, 2, 0.0);
388 matrix_.set(2, 3, 0.0);
391 Vector2dF Transform::To2dTranslation() const {
392 return gfx::Vector2dF(SkMScalarToFloat(matrix_.get(0, 3)),
393 SkMScalarToFloat(matrix_.get(1, 3)));
396 void Transform::TransformPoint(Point* point) const {
397 DCHECK(point);
398 TransformPointInternal(matrix_, point);
401 void Transform::TransformPoint(Point3F* point) const {
402 DCHECK(point);
403 TransformPointInternal(matrix_, point);
406 bool Transform::TransformPointReverse(Point* point) const {
407 DCHECK(point);
409 // TODO(sad): Try to avoid trying to invert the matrix.
410 SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
411 if (!matrix_.invert(&inverse))
412 return false;
414 TransformPointInternal(inverse, point);
415 return true;
418 bool Transform::TransformPointReverse(Point3F* point) const {
419 DCHECK(point);
421 // TODO(sad): Try to avoid trying to invert the matrix.
422 SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
423 if (!matrix_.invert(&inverse))
424 return false;
426 TransformPointInternal(inverse, point);
427 return true;
430 void Transform::TransformRect(RectF* rect) const {
431 if (matrix_.isIdentity())
432 return;
434 SkRect src = RectFToSkRect(*rect);
435 const SkMatrix& matrix = matrix_;
436 matrix.mapRect(&src);
437 *rect = SkRectToRectF(src);
440 bool Transform::TransformRectReverse(RectF* rect) const {
441 if (matrix_.isIdentity())
442 return true;
444 SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
445 if (!matrix_.invert(&inverse))
446 return false;
448 const SkMatrix& matrix = inverse;
449 SkRect src = RectFToSkRect(*rect);
450 matrix.mapRect(&src);
451 *rect = SkRectToRectF(src);
452 return true;
455 void Transform::TransformBox(BoxF* box) const {
456 BoxF bounds;
457 bool first_point = true;
458 for (int corner = 0; corner < 8; ++corner) {
459 gfx::Point3F point = box->origin();
460 point += gfx::Vector3dF(corner & 1 ? box->width() : 0.f,
461 corner & 2 ? box->height() : 0.f,
462 corner & 4 ? box->depth() : 0.f);
463 TransformPoint(&point);
464 if (first_point) {
465 bounds.set_origin(point);
466 first_point = false;
467 } else {
468 bounds.ExpandTo(point);
471 *box = bounds;
474 bool Transform::TransformBoxReverse(BoxF* box) const {
475 gfx::Transform inverse = *this;
476 if (!GetInverse(&inverse))
477 return false;
478 inverse.TransformBox(box);
479 return true;
482 bool Transform::Blend(const Transform& from, double progress) {
483 DecomposedTransform to_decomp;
484 DecomposedTransform from_decomp;
485 if (!DecomposeTransform(&to_decomp, *this) ||
486 !DecomposeTransform(&from_decomp, from))
487 return false;
489 if (!BlendDecomposedTransforms(&to_decomp, to_decomp, from_decomp, progress))
490 return false;
492 matrix_ = ComposeTransform(to_decomp).matrix();
493 return true;
496 void Transform::TransformPointInternal(const SkMatrix44& xform,
497 Point3F* point) const {
498 if (xform.isIdentity())
499 return;
501 SkMScalar p[4] = {SkFloatToMScalar(point->x()), SkFloatToMScalar(point->y()),
502 SkFloatToMScalar(point->z()), 1};
504 xform.mapMScalars(p);
506 if (p[3] != SK_MScalar1 && p[3] != 0.f) {
507 float w_inverse = SK_MScalar1 / p[3];
508 point->SetPoint(p[0] * w_inverse, p[1] * w_inverse, p[2] * w_inverse);
509 } else {
510 point->SetPoint(p[0], p[1], p[2]);
514 void Transform::TransformPointInternal(const SkMatrix44& xform,
515 Point* point) const {
516 if (xform.isIdentity())
517 return;
519 SkMScalar p[4] = {SkIntToMScalar(point->x()), SkIntToMScalar(point->y()),
520 0, 1};
522 xform.mapMScalars(p);
524 point->SetPoint(ToRoundedInt(p[0]), ToRoundedInt(p[1]));
527 std::string Transform::ToString() const {
528 return base::StringPrintf(
529 "[ %+0.4f %+0.4f %+0.4f %+0.4f \n"
530 " %+0.4f %+0.4f %+0.4f %+0.4f \n"
531 " %+0.4f %+0.4f %+0.4f %+0.4f \n"
532 " %+0.4f %+0.4f %+0.4f %+0.4f ]\n",
533 matrix_.get(0, 0),
534 matrix_.get(0, 1),
535 matrix_.get(0, 2),
536 matrix_.get(0, 3),
537 matrix_.get(1, 0),
538 matrix_.get(1, 1),
539 matrix_.get(1, 2),
540 matrix_.get(1, 3),
541 matrix_.get(2, 0),
542 matrix_.get(2, 1),
543 matrix_.get(2, 2),
544 matrix_.get(2, 3),
545 matrix_.get(3, 0),
546 matrix_.get(3, 1),
547 matrix_.get(3, 2),
548 matrix_.get(3, 3));
551 } // namespace gfx