Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / gfx / transform.cc
blobc42c961dc29307c58325a547e579911daadbb2a7
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 static float Round(float f) {
44 if (f == 0.f)
45 return f;
46 return (f > 0.f) ? std::floor(f + 0.5f) : std::ceil(f - 0.5f);
49 } // namespace
51 Transform::Transform(SkMScalar col1row1,
52 SkMScalar col2row1,
53 SkMScalar col3row1,
54 SkMScalar col4row1,
55 SkMScalar col1row2,
56 SkMScalar col2row2,
57 SkMScalar col3row2,
58 SkMScalar col4row2,
59 SkMScalar col1row3,
60 SkMScalar col2row3,
61 SkMScalar col3row3,
62 SkMScalar col4row3,
63 SkMScalar col1row4,
64 SkMScalar col2row4,
65 SkMScalar col3row4,
66 SkMScalar col4row4)
67 : matrix_(SkMatrix44::kUninitialized_Constructor) {
68 matrix_.set(0, 0, col1row1);
69 matrix_.set(1, 0, col1row2);
70 matrix_.set(2, 0, col1row3);
71 matrix_.set(3, 0, col1row4);
73 matrix_.set(0, 1, col2row1);
74 matrix_.set(1, 1, col2row2);
75 matrix_.set(2, 1, col2row3);
76 matrix_.set(3, 1, col2row4);
78 matrix_.set(0, 2, col3row1);
79 matrix_.set(1, 2, col3row2);
80 matrix_.set(2, 2, col3row3);
81 matrix_.set(3, 2, col3row4);
83 matrix_.set(0, 3, col4row1);
84 matrix_.set(1, 3, col4row2);
85 matrix_.set(2, 3, col4row3);
86 matrix_.set(3, 3, col4row4);
89 Transform::Transform(SkMScalar col1row1,
90 SkMScalar col2row1,
91 SkMScalar col1row2,
92 SkMScalar col2row2,
93 SkMScalar x_translation,
94 SkMScalar y_translation)
95 : matrix_(SkMatrix44::kIdentity_Constructor) {
96 matrix_.set(0, 0, col1row1);
97 matrix_.set(1, 0, col1row2);
98 matrix_.set(0, 1, col2row1);
99 matrix_.set(1, 1, col2row2);
100 matrix_.set(0, 3, x_translation);
101 matrix_.set(1, 3, y_translation);
104 void Transform::RotateAboutXAxis(double degrees) {
105 double radians = degrees * M_PI / 180;
106 SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians));
107 SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians));
108 if (matrix_.isIdentity()) {
109 matrix_.set3x3(1, 0, 0,
110 0, cosTheta, sinTheta,
111 0, -sinTheta, cosTheta);
112 } else {
113 SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
114 rot.set3x3(1, 0, 0,
115 0, cosTheta, sinTheta,
116 0, -sinTheta, cosTheta);
117 matrix_.preConcat(rot);
121 void Transform::RotateAboutYAxis(double degrees) {
122 double radians = degrees * M_PI / 180;
123 SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians));
124 SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians));
125 if (matrix_.isIdentity()) {
126 // Note carefully the placement of the -sinTheta for rotation about
127 // y-axis is different than rotation about x-axis or z-axis.
128 matrix_.set3x3(cosTheta, 0, -sinTheta,
129 0, 1, 0,
130 sinTheta, 0, cosTheta);
131 } else {
132 SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
133 rot.set3x3(cosTheta, 0, -sinTheta,
134 0, 1, 0,
135 sinTheta, 0, cosTheta);
136 matrix_.preConcat(rot);
140 void Transform::RotateAboutZAxis(double degrees) {
141 double radians = degrees * M_PI / 180;
142 SkMScalar cosTheta = SkDoubleToMScalar(std::cos(radians));
143 SkMScalar sinTheta = SkDoubleToMScalar(std::sin(radians));
144 if (matrix_.isIdentity()) {
145 matrix_.set3x3(cosTheta, sinTheta, 0,
146 -sinTheta, cosTheta, 0,
147 0, 0, 1);
148 } else {
149 SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
150 rot.set3x3(cosTheta, sinTheta, 0,
151 -sinTheta, cosTheta, 0,
152 0, 0, 1);
153 matrix_.preConcat(rot);
157 void Transform::RotateAbout(const Vector3dF& axis, double degrees) {
158 if (matrix_.isIdentity()) {
159 matrix_.setRotateDegreesAbout(SkFloatToMScalar(axis.x()),
160 SkFloatToMScalar(axis.y()),
161 SkFloatToMScalar(axis.z()),
162 SkDoubleToMScalar(degrees));
163 } else {
164 SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
165 rot.setRotateDegreesAbout(SkFloatToMScalar(axis.x()),
166 SkFloatToMScalar(axis.y()),
167 SkFloatToMScalar(axis.z()),
168 SkDoubleToMScalar(degrees));
169 matrix_.preConcat(rot);
173 void Transform::Scale(SkMScalar x, SkMScalar y) { matrix_.preScale(x, y, 1); }
175 void Transform::Scale3d(SkMScalar x, SkMScalar y, SkMScalar z) {
176 matrix_.preScale(x, y, z);
179 void Transform::Translate(SkMScalar x, SkMScalar y) {
180 matrix_.preTranslate(x, y, 0);
183 void Transform::Translate3d(SkMScalar x, SkMScalar y, SkMScalar z) {
184 matrix_.preTranslate(x, y, z);
187 void Transform::Skew(double angle_x, double angle_y) {
188 if (matrix_.isIdentity()) {
189 matrix_.set(0, 1, TanDegrees(angle_x));
190 matrix_.set(1, 0, TanDegrees(angle_y));
191 } else {
192 SkMatrix44 skew(SkMatrix44::kIdentity_Constructor);
193 skew.set(0, 1, TanDegrees(angle_x));
194 skew.set(1, 0, TanDegrees(angle_y));
195 matrix_.preConcat(skew);
199 void Transform::ApplyPerspectiveDepth(SkMScalar depth) {
200 if (depth == 0)
201 return;
202 if (matrix_.isIdentity()) {
203 matrix_.set(3, 2, -SK_MScalar1 / depth);
204 } else {
205 SkMatrix44 m(SkMatrix44::kIdentity_Constructor);
206 m.set(3, 2, -SK_MScalar1 / depth);
207 matrix_.preConcat(m);
211 void Transform::PreconcatTransform(const Transform& transform) {
212 matrix_.preConcat(transform.matrix_);
215 void Transform::ConcatTransform(const Transform& transform) {
216 matrix_.postConcat(transform.matrix_);
219 bool Transform::IsApproximatelyIdentityOrTranslation(
220 SkMScalar tolerance) const {
221 DCHECK_GE(tolerance, 0);
222 return
223 ApproximatelyOne(matrix_.get(0, 0), tolerance) &&
224 ApproximatelyZero(matrix_.get(1, 0), tolerance) &&
225 ApproximatelyZero(matrix_.get(2, 0), tolerance) &&
226 matrix_.get(3, 0) == 0 &&
227 ApproximatelyZero(matrix_.get(0, 1), tolerance) &&
228 ApproximatelyOne(matrix_.get(1, 1), tolerance) &&
229 ApproximatelyZero(matrix_.get(2, 1), tolerance) &&
230 matrix_.get(3, 1) == 0 &&
231 ApproximatelyZero(matrix_.get(0, 2), tolerance) &&
232 ApproximatelyZero(matrix_.get(1, 2), tolerance) &&
233 ApproximatelyOne(matrix_.get(2, 2), tolerance) &&
234 matrix_.get(3, 2) == 0 &&
235 matrix_.get(3, 3) == 1;
238 bool Transform::IsIdentityOrIntegerTranslation() const {
239 if (!IsIdentityOrTranslation())
240 return false;
242 bool no_fractional_translation =
243 static_cast<int>(matrix_.get(0, 3)) == matrix_.get(0, 3) &&
244 static_cast<int>(matrix_.get(1, 3)) == matrix_.get(1, 3) &&
245 static_cast<int>(matrix_.get(2, 3)) == matrix_.get(2, 3);
247 return no_fractional_translation;
250 bool Transform::IsBackFaceVisible() const {
251 // Compute whether a layer with a forward-facing normal of (0, 0, 1, 0)
252 // would have its back face visible after applying the transform.
253 if (matrix_.isIdentity())
254 return false;
256 // This is done by transforming the normal and seeing if the resulting z
257 // value is positive or negative. However, note that transforming a normal
258 // actually requires using the inverse-transpose of the original transform.
260 // We can avoid inverting and transposing the matrix since we know we want
261 // to transform only the specific normal vector (0, 0, 1, 0). In this case,
262 // we only need the 3rd row, 3rd column of the inverse-transpose. We can
263 // calculate only the 3rd row 3rd column element of the inverse, skipping
264 // everything else.
266 // For more information, refer to:
267 // http://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution
270 double determinant = matrix_.determinant();
272 // If matrix was not invertible, then just assume back face is not visible.
273 if (std::abs(determinant) <= kEpsilon)
274 return false;
276 // Compute the cofactor of the 3rd row, 3rd column.
277 double cofactor_part_1 =
278 matrix_.get(0, 0) * matrix_.get(1, 1) * matrix_.get(3, 3);
280 double cofactor_part_2 =
281 matrix_.get(0, 1) * matrix_.get(1, 3) * matrix_.get(3, 0);
283 double cofactor_part_3 =
284 matrix_.get(0, 3) * matrix_.get(1, 0) * matrix_.get(3, 1);
286 double cofactor_part_4 =
287 matrix_.get(0, 0) * matrix_.get(1, 3) * matrix_.get(3, 1);
289 double cofactor_part_5 =
290 matrix_.get(0, 1) * matrix_.get(1, 0) * matrix_.get(3, 3);
292 double cofactor_part_6 =
293 matrix_.get(0, 3) * matrix_.get(1, 1) * matrix_.get(3, 0);
295 double cofactor33 =
296 cofactor_part_1 +
297 cofactor_part_2 +
298 cofactor_part_3 -
299 cofactor_part_4 -
300 cofactor_part_5 -
301 cofactor_part_6;
303 // Technically the transformed z component is cofactor33 / determinant. But
304 // we can avoid the costly division because we only care about the resulting
305 // +/- sign; we can check this equivalently by multiplication.
306 return cofactor33 * determinant < 0;
309 bool Transform::GetInverse(Transform* transform) const {
310 if (!matrix_.invert(&transform->matrix_)) {
311 // Initialize the return value to identity if this matrix turned
312 // out to be un-invertible.
313 transform->MakeIdentity();
314 return false;
317 return true;
320 bool Transform::Preserves2dAxisAlignment() const {
321 // Check whether an axis aligned 2-dimensional rect would remain axis-aligned
322 // after being transformed by this matrix (and implicitly projected by
323 // dropping any non-zero z-values).
325 // The 4th column can be ignored because translations don't affect axis
326 // alignment. The 3rd column can be ignored because we are assuming 2d
327 // inputs, where z-values will be zero. The 3rd row can also be ignored
328 // because we are assuming 2d outputs, and any resulting z-value is dropped
329 // anyway. For the inner 2x2 portion, the only effects that keep a rect axis
330 // aligned are (1) swapping axes and (2) scaling axes. This can be checked by
331 // verifying only 1 element of every column and row is non-zero. Degenerate
332 // cases that project the x or y dimension to zero are considered to preserve
333 // axis alignment.
335 // If the matrix does have perspective component that is affected by x or y
336 // values: The current implementation conservatively assumes that axis
337 // alignment is not preserved.
339 bool has_x_or_y_perspective =
340 matrix_.get(3, 0) != 0 || matrix_.get(3, 1) != 0;
342 int num_non_zero_in_row_0 = 0;
343 int num_non_zero_in_row_1 = 0;
344 int num_non_zero_in_col_0 = 0;
345 int num_non_zero_in_col_1 = 0;
347 if (std::abs(matrix_.get(0, 0)) > kEpsilon) {
348 num_non_zero_in_row_0++;
349 num_non_zero_in_col_0++;
352 if (std::abs(matrix_.get(0, 1)) > kEpsilon) {
353 num_non_zero_in_row_0++;
354 num_non_zero_in_col_1++;
357 if (std::abs(matrix_.get(1, 0)) > kEpsilon) {
358 num_non_zero_in_row_1++;
359 num_non_zero_in_col_0++;
362 if (std::abs(matrix_.get(1, 1)) > kEpsilon) {
363 num_non_zero_in_row_1++;
364 num_non_zero_in_col_1++;
367 return
368 num_non_zero_in_row_0 <= 1 &&
369 num_non_zero_in_row_1 <= 1 &&
370 num_non_zero_in_col_0 <= 1 &&
371 num_non_zero_in_col_1 <= 1 &&
372 !has_x_or_y_perspective;
375 void Transform::Transpose() {
376 matrix_.transpose();
379 void Transform::FlattenTo2d() {
380 matrix_.set(2, 0, 0.0);
381 matrix_.set(2, 1, 0.0);
382 matrix_.set(0, 2, 0.0);
383 matrix_.set(1, 2, 0.0);
384 matrix_.set(2, 2, 1.0);
385 matrix_.set(3, 2, 0.0);
386 matrix_.set(2, 3, 0.0);
389 bool Transform::IsFlat() const {
390 return matrix_.get(2, 0) == 0.0 && matrix_.get(2, 1) == 0.0 &&
391 matrix_.get(0, 2) == 0.0 && matrix_.get(1, 2) == 0.0 &&
392 matrix_.get(2, 2) == 1.0 && matrix_.get(3, 2) == 0.0 &&
393 matrix_.get(2, 3) == 0.0;
396 Vector2dF Transform::To2dTranslation() const {
397 return gfx::Vector2dF(SkMScalarToFloat(matrix_.get(0, 3)),
398 SkMScalarToFloat(matrix_.get(1, 3)));
401 void Transform::TransformPoint(Point* point) const {
402 DCHECK(point);
403 TransformPointInternal(matrix_, point);
406 void Transform::TransformPoint(Point3F* point) const {
407 DCHECK(point);
408 TransformPointInternal(matrix_, point);
411 bool Transform::TransformPointReverse(Point* point) const {
412 DCHECK(point);
414 // TODO(sad): Try to avoid trying to invert the matrix.
415 SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
416 if (!matrix_.invert(&inverse))
417 return false;
419 TransformPointInternal(inverse, point);
420 return true;
423 bool Transform::TransformPointReverse(Point3F* point) const {
424 DCHECK(point);
426 // TODO(sad): Try to avoid trying to invert the matrix.
427 SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
428 if (!matrix_.invert(&inverse))
429 return false;
431 TransformPointInternal(inverse, point);
432 return true;
435 void Transform::TransformRect(RectF* rect) const {
436 if (matrix_.isIdentity())
437 return;
439 SkRect src = RectFToSkRect(*rect);
440 const SkMatrix& matrix = matrix_;
441 matrix.mapRect(&src);
442 *rect = SkRectToRectF(src);
445 bool Transform::TransformRectReverse(RectF* rect) const {
446 if (matrix_.isIdentity())
447 return true;
449 SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
450 if (!matrix_.invert(&inverse))
451 return false;
453 const SkMatrix& matrix = inverse;
454 SkRect src = RectFToSkRect(*rect);
455 matrix.mapRect(&src);
456 *rect = SkRectToRectF(src);
457 return true;
460 void Transform::TransformBox(BoxF* box) const {
461 BoxF bounds;
462 bool first_point = true;
463 for (int corner = 0; corner < 8; ++corner) {
464 gfx::Point3F point = box->origin();
465 point += gfx::Vector3dF(corner & 1 ? box->width() : 0.f,
466 corner & 2 ? box->height() : 0.f,
467 corner & 4 ? box->depth() : 0.f);
468 TransformPoint(&point);
469 if (first_point) {
470 bounds.set_origin(point);
471 first_point = false;
472 } else {
473 bounds.ExpandTo(point);
476 *box = bounds;
479 bool Transform::TransformBoxReverse(BoxF* box) const {
480 gfx::Transform inverse = *this;
481 if (!GetInverse(&inverse))
482 return false;
483 inverse.TransformBox(box);
484 return true;
487 bool Transform::Blend(const Transform& from, double progress) {
488 DecomposedTransform to_decomp;
489 DecomposedTransform from_decomp;
490 if (!DecomposeTransform(&to_decomp, *this) ||
491 !DecomposeTransform(&from_decomp, from))
492 return false;
494 if (!BlendDecomposedTransforms(&to_decomp, to_decomp, from_decomp, progress))
495 return false;
497 matrix_ = ComposeTransform(to_decomp).matrix();
498 return true;
501 void Transform::RoundTranslationComponents() {
502 matrix_.set(0, 3, Round(matrix_.get(0, 3)));
503 matrix_.set(1, 3, Round(matrix_.get(1, 3)));
506 void Transform::TransformPointInternal(const SkMatrix44& xform,
507 Point3F* point) const {
508 if (xform.isIdentity())
509 return;
511 SkMScalar p[4] = {SkFloatToMScalar(point->x()), SkFloatToMScalar(point->y()),
512 SkFloatToMScalar(point->z()), 1};
514 xform.mapMScalars(p);
516 if (p[3] != SK_MScalar1 && p[3] != 0.f) {
517 float w_inverse = SK_MScalar1 / p[3];
518 point->SetPoint(p[0] * w_inverse, p[1] * w_inverse, p[2] * w_inverse);
519 } else {
520 point->SetPoint(p[0], p[1], p[2]);
524 void Transform::TransformPointInternal(const SkMatrix44& xform,
525 Point* point) const {
526 if (xform.isIdentity())
527 return;
529 SkMScalar p[4] = {SkIntToMScalar(point->x()), SkIntToMScalar(point->y()),
530 0, 1};
532 xform.mapMScalars(p);
534 point->SetPoint(ToRoundedInt(p[0]), ToRoundedInt(p[1]));
537 std::string Transform::ToString() const {
538 return base::StringPrintf(
539 "[ %+0.4f %+0.4f %+0.4f %+0.4f \n"
540 " %+0.4f %+0.4f %+0.4f %+0.4f \n"
541 " %+0.4f %+0.4f %+0.4f %+0.4f \n"
542 " %+0.4f %+0.4f %+0.4f %+0.4f ]\n",
543 matrix_.get(0, 0),
544 matrix_.get(0, 1),
545 matrix_.get(0, 2),
546 matrix_.get(0, 3),
547 matrix_.get(1, 0),
548 matrix_.get(1, 1),
549 matrix_.get(1, 2),
550 matrix_.get(1, 3),
551 matrix_.get(2, 0),
552 matrix_.get(2, 1),
553 matrix_.get(2, 2),
554 matrix_.get(2, 3),
555 matrix_.get(3, 0),
556 matrix_.get(3, 1),
557 matrix_.get(3, 2),
558 matrix_.get(3, 3));
561 } // namespace gfx