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 #include "ui/gfx/transform_util.h"
11 #include "base/logging.h"
12 #include "base/strings/stringprintf.h"
13 #include "ui/gfx/geometry/point.h"
14 #include "ui/gfx/geometry/point3_f.h"
15 #include "ui/gfx/geometry/rect.h"
21 SkMScalar
Length3(SkMScalar v
[3]) {
22 double vd
[3] = {SkMScalarToDouble(v
[0]), SkMScalarToDouble(v
[1]),
23 SkMScalarToDouble(v
[2])};
24 return SkDoubleToMScalar(
25 std::sqrt(vd
[0] * vd
[0] + vd
[1] * vd
[1] + vd
[2] * vd
[2]));
29 SkMScalar
Dot(const SkMScalar
* a
, const SkMScalar
* b
) {
31 for (int i
= 0; i
< n
; ++i
)
33 return SkDoubleToMScalar(total
);
37 void Combine(SkMScalar
* out
,
42 for (int i
= 0; i
< n
; ++i
)
43 out
[i
] = SkDoubleToMScalar(a
[i
] * scale_a
+ b
[i
] * scale_b
);
46 void Cross3(SkMScalar out
[3], SkMScalar a
[3], SkMScalar b
[3]) {
47 SkMScalar x
= a
[1] * b
[2] - a
[2] * b
[1];
48 SkMScalar y
= a
[2] * b
[0] - a
[0] * b
[2];
49 SkMScalar z
= a
[0] * b
[1] - a
[1] * b
[0];
55 SkMScalar
Round(SkMScalar n
) {
56 return SkDoubleToMScalar(std::floor(SkMScalarToDouble(n
) + 0.5));
59 // Taken from http://www.w3.org/TR/css3-transforms/.
60 bool Slerp(SkMScalar out
[4],
61 const SkMScalar q1
[4],
62 const SkMScalar q2
[4],
64 double product
= Dot
<4>(q1
, q2
);
66 // Clamp product to -1.0 <= product <= 1.0.
67 product
= std::min(std::max(product
, -1.0), 1.0);
69 const double epsilon
= 1e-5;
70 if (std::abs(product
- 1.0) < epsilon
) {
71 for (int i
= 0; i
< 4; ++i
)
76 double denom
= std::sqrt(1.0 - product
* product
);
77 double theta
= std::acos(product
);
78 double w
= std::sin(progress
* theta
) * (1.0 / denom
);
80 double scale1
= std::cos(progress
* theta
) - product
* w
;
82 Combine
<4>(out
, q1
, q2
, scale1
, scale2
);
87 // Returns false if the matrix cannot be normalized.
88 bool Normalize(SkMatrix44
& m
) {
89 if (m
.get(3, 3) == 0.0)
93 SkMScalar scale
= SK_MScalar1
/ m
.get(3, 3);
94 for (int i
= 0; i
< 4; i
++)
95 for (int j
= 0; j
< 4; j
++)
96 m
.set(i
, j
, m
.get(i
, j
) * scale
);
101 SkMatrix44
BuildPerspectiveMatrix(const DecomposedTransform
& decomp
) {
102 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
104 for (int i
= 0; i
< 4; i
++)
105 matrix
.setDouble(3, i
, decomp
.perspective
[i
]);
109 SkMatrix44
BuildTranslationMatrix(const DecomposedTransform
& decomp
) {
110 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
111 // Implicitly calls matrix.setIdentity()
112 matrix
.setTranslate(SkDoubleToMScalar(decomp
.translate
[0]),
113 SkDoubleToMScalar(decomp
.translate
[1]),
114 SkDoubleToMScalar(decomp
.translate
[2]));
118 SkMatrix44
BuildSnappedTranslationMatrix(DecomposedTransform decomp
) {
119 decomp
.translate
[0] = Round(decomp
.translate
[0]);
120 decomp
.translate
[1] = Round(decomp
.translate
[1]);
121 decomp
.translate
[2] = Round(decomp
.translate
[2]);
122 return BuildTranslationMatrix(decomp
);
125 SkMatrix44
BuildRotationMatrix(const DecomposedTransform
& decomp
) {
126 double x
= decomp
.quaternion
[0];
127 double y
= decomp
.quaternion
[1];
128 double z
= decomp
.quaternion
[2];
129 double w
= decomp
.quaternion
[3];
131 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
133 // Implicitly calls matrix.setIdentity()
134 matrix
.set3x3(SkDoubleToMScalar(1.0 - 2.0 * (y
* y
+ z
* z
)),
135 SkDoubleToMScalar(2.0 * (x
* y
+ z
* w
)),
136 SkDoubleToMScalar(2.0 * (x
* z
- y
* w
)),
137 SkDoubleToMScalar(2.0 * (x
* y
- z
* w
)),
138 SkDoubleToMScalar(1.0 - 2.0 * (x
* x
+ z
* z
)),
139 SkDoubleToMScalar(2.0 * (y
* z
+ x
* w
)),
140 SkDoubleToMScalar(2.0 * (x
* z
+ y
* w
)),
141 SkDoubleToMScalar(2.0 * (y
* z
- x
* w
)),
142 SkDoubleToMScalar(1.0 - 2.0 * (x
* x
+ y
* y
)));
146 SkMatrix44
BuildSnappedRotationMatrix(const DecomposedTransform
& decomp
) {
147 // Create snapped rotation.
148 SkMatrix44 rotation_matrix
= BuildRotationMatrix(decomp
);
149 for (int i
= 0; i
< 3; ++i
) {
150 for (int j
= 0; j
< 3; ++j
) {
151 SkMScalar value
= rotation_matrix
.get(i
, j
);
152 // Snap values to -1, 0 or 1.
155 } else if (value
> 0.5f
) {
160 rotation_matrix
.set(i
, j
, value
);
163 return rotation_matrix
;
166 SkMatrix44
BuildSkewMatrix(const DecomposedTransform
& decomp
) {
167 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
169 SkMatrix44
temp(SkMatrix44::kIdentity_Constructor
);
170 if (decomp
.skew
[2]) {
171 temp
.setDouble(1, 2, decomp
.skew
[2]);
172 matrix
.preConcat(temp
);
175 if (decomp
.skew
[1]) {
176 temp
.setDouble(1, 2, 0);
177 temp
.setDouble(0, 2, decomp
.skew
[1]);
178 matrix
.preConcat(temp
);
181 if (decomp
.skew
[0]) {
182 temp
.setDouble(0, 2, 0);
183 temp
.setDouble(0, 1, decomp
.skew
[0]);
184 matrix
.preConcat(temp
);
189 SkMatrix44
BuildScaleMatrix(const DecomposedTransform
& decomp
) {
190 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
191 matrix
.setScale(SkDoubleToMScalar(decomp
.scale
[0]),
192 SkDoubleToMScalar(decomp
.scale
[1]),
193 SkDoubleToMScalar(decomp
.scale
[2]));
197 SkMatrix44
BuildSnappedScaleMatrix(DecomposedTransform decomp
) {
198 decomp
.scale
[0] = Round(decomp
.scale
[0]);
199 decomp
.scale
[1] = Round(decomp
.scale
[1]);
200 decomp
.scale
[2] = Round(decomp
.scale
[2]);
201 return BuildScaleMatrix(decomp
);
204 Transform
ComposeTransform(const SkMatrix44
& perspective
,
205 const SkMatrix44
& translation
,
206 const SkMatrix44
& rotation
,
207 const SkMatrix44
& skew
,
208 const SkMatrix44
& scale
) {
209 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
211 matrix
.preConcat(perspective
);
212 matrix
.preConcat(translation
);
213 matrix
.preConcat(rotation
);
214 matrix
.preConcat(skew
);
215 matrix
.preConcat(scale
);
218 to_return
.matrix() = matrix
;
222 bool CheckViewportPointMapsWithinOnePixel(const Point
& point
,
223 const Transform
& transform
) {
224 Point3F
point_original(point
);
225 Point3F
point_transformed(point
);
227 // Can't use TransformRect here since it would give us the axis-aligned
228 // bounding rect of the 4 points in the initial rectable which is not what we
230 transform
.TransformPoint(&point_transformed
);
232 if ((point_transformed
- point_original
).Length() > 1.f
) {
233 // The changed distance should not be more than 1 pixel.
239 bool CheckTransformsMapsIntViewportWithinOnePixel(const Rect
& viewport
,
240 const Transform
& original
,
241 const Transform
& snapped
) {
243 Transform
original_inv(Transform::kSkipInitialization
);
244 bool invertible
= true;
245 invertible
&= original
.GetInverse(&original_inv
);
246 DCHECK(invertible
) << "Non-invertible transform, cannot snap.";
248 Transform combined
= snapped
* original_inv
;
250 return CheckViewportPointMapsWithinOnePixel(viewport
.origin(), combined
) &&
251 CheckViewportPointMapsWithinOnePixel(viewport
.top_right(), combined
) &&
252 CheckViewportPointMapsWithinOnePixel(viewport
.bottom_left(),
254 CheckViewportPointMapsWithinOnePixel(viewport
.bottom_right(),
260 Transform
GetScaleTransform(const Point
& anchor
, float scale
) {
262 transform
.Translate(anchor
.x() * (1 - scale
),
263 anchor
.y() * (1 - scale
));
264 transform
.Scale(scale
, scale
);
268 DecomposedTransform::DecomposedTransform() {
269 translate
[0] = translate
[1] = translate
[2] = 0.0;
270 scale
[0] = scale
[1] = scale
[2] = 1.0;
271 skew
[0] = skew
[1] = skew
[2] = 0.0;
272 perspective
[0] = perspective
[1] = perspective
[2] = 0.0;
273 quaternion
[0] = quaternion
[1] = quaternion
[2] = 0.0;
274 perspective
[3] = quaternion
[3] = 1.0;
277 bool BlendDecomposedTransforms(DecomposedTransform
* out
,
278 const DecomposedTransform
& to
,
279 const DecomposedTransform
& from
,
281 double scalea
= progress
;
282 double scaleb
= 1.0 - progress
;
283 Combine
<3>(out
->translate
, to
.translate
, from
.translate
, scalea
, scaleb
);
284 Combine
<3>(out
->scale
, to
.scale
, from
.scale
, scalea
, scaleb
);
285 Combine
<3>(out
->skew
, to
.skew
, from
.skew
, scalea
, scaleb
);
287 out
->perspective
, to
.perspective
, from
.perspective
, scalea
, scaleb
);
288 return Slerp(out
->quaternion
, from
.quaternion
, to
.quaternion
, progress
);
291 // Taken from http://www.w3.org/TR/css3-transforms/.
292 bool DecomposeTransform(DecomposedTransform
* decomp
,
293 const Transform
& transform
) {
297 // We'll operate on a copy of the matrix.
298 SkMatrix44 matrix
= transform
.matrix();
300 // If we cannot normalize the matrix, then bail early as we cannot decompose.
301 if (!Normalize(matrix
))
304 SkMatrix44 perspectiveMatrix
= matrix
;
306 for (int i
= 0; i
< 3; ++i
)
307 perspectiveMatrix
.set(3, i
, 0.0);
309 perspectiveMatrix
.set(3, 3, 1.0);
311 // If the perspective matrix is not invertible, we are also unable to
312 // decompose, so we'll bail early. Constant taken from SkMatrix44::invert.
313 if (std::abs(perspectiveMatrix
.determinant()) < 1e-8)
316 if (matrix
.get(3, 0) != 0.0 || matrix
.get(3, 1) != 0.0 ||
317 matrix
.get(3, 2) != 0.0) {
318 // rhs is the right hand side of the equation.
326 // Solve the equation by inverting perspectiveMatrix and multiplying
327 // rhs by the inverse.
328 SkMatrix44
inversePerspectiveMatrix(SkMatrix44::kUninitialized_Constructor
);
329 if (!perspectiveMatrix
.invert(&inversePerspectiveMatrix
))
332 SkMatrix44 transposedInversePerspectiveMatrix
=
333 inversePerspectiveMatrix
;
335 transposedInversePerspectiveMatrix
.transpose();
336 transposedInversePerspectiveMatrix
.mapMScalars(rhs
);
338 for (int i
= 0; i
< 4; ++i
)
339 decomp
->perspective
[i
] = rhs
[i
];
343 for (int i
= 0; i
< 3; ++i
)
344 decomp
->perspective
[i
] = 0.0;
345 decomp
->perspective
[3] = 1.0;
348 for (int i
= 0; i
< 3; i
++)
349 decomp
->translate
[i
] = matrix
.get(i
, 3);
352 for (int i
= 0; i
< 3; i
++)
353 for (int j
= 0; j
< 3; ++j
)
354 row
[i
][j
] = matrix
.get(j
, i
);
356 // Compute X scale factor and normalize first row.
357 decomp
->scale
[0] = Length3(row
[0]);
358 if (decomp
->scale
[0] != 0.0) {
359 row
[0][0] /= decomp
->scale
[0];
360 row
[0][1] /= decomp
->scale
[0];
361 row
[0][2] /= decomp
->scale
[0];
364 // Compute XY shear factor and make 2nd row orthogonal to 1st.
365 decomp
->skew
[0] = Dot
<3>(row
[0], row
[1]);
366 Combine
<3>(row
[1], row
[1], row
[0], 1.0, -decomp
->skew
[0]);
368 // Now, compute Y scale and normalize 2nd row.
369 decomp
->scale
[1] = Length3(row
[1]);
370 if (decomp
->scale
[1] != 0.0) {
371 row
[1][0] /= decomp
->scale
[1];
372 row
[1][1] /= decomp
->scale
[1];
373 row
[1][2] /= decomp
->scale
[1];
376 decomp
->skew
[0] /= decomp
->scale
[1];
378 // Compute XZ and YZ shears, orthogonalize 3rd row
379 decomp
->skew
[1] = Dot
<3>(row
[0], row
[2]);
380 Combine
<3>(row
[2], row
[2], row
[0], 1.0, -decomp
->skew
[1]);
381 decomp
->skew
[2] = Dot
<3>(row
[1], row
[2]);
382 Combine
<3>(row
[2], row
[2], row
[1], 1.0, -decomp
->skew
[2]);
384 // Next, get Z scale and normalize 3rd row.
385 decomp
->scale
[2] = Length3(row
[2]);
386 if (decomp
->scale
[2] != 0.0) {
387 row
[2][0] /= decomp
->scale
[2];
388 row
[2][1] /= decomp
->scale
[2];
389 row
[2][2] /= decomp
->scale
[2];
392 decomp
->skew
[1] /= decomp
->scale
[2];
393 decomp
->skew
[2] /= decomp
->scale
[2];
395 // At this point, the matrix (in rows) is orthonormal.
396 // Check for a coordinate system flip. If the determinant
397 // is -1, then negate the matrix and the scaling factors.
399 Cross3(pdum3
, row
[1], row
[2]);
400 if (Dot
<3>(row
[0], pdum3
) < 0) {
401 for (int i
= 0; i
< 3; i
++) {
402 decomp
->scale
[i
] *= -1.0;
403 for (int j
= 0; j
< 3; ++j
)
408 double row00
= SkMScalarToDouble(row
[0][0]);
409 double row11
= SkMScalarToDouble(row
[1][1]);
410 double row22
= SkMScalarToDouble(row
[2][2]);
411 decomp
->quaternion
[0] = SkDoubleToMScalar(
412 0.5 * std::sqrt(std::max(1.0 + row00
- row11
- row22
, 0.0)));
413 decomp
->quaternion
[1] = SkDoubleToMScalar(
414 0.5 * std::sqrt(std::max(1.0 - row00
+ row11
- row22
, 0.0)));
415 decomp
->quaternion
[2] = SkDoubleToMScalar(
416 0.5 * std::sqrt(std::max(1.0 - row00
- row11
+ row22
, 0.0)));
417 decomp
->quaternion
[3] = SkDoubleToMScalar(
418 0.5 * std::sqrt(std::max(1.0 + row00
+ row11
+ row22
, 0.0)));
420 if (row
[2][1] > row
[1][2])
421 decomp
->quaternion
[0] = -decomp
->quaternion
[0];
422 if (row
[0][2] > row
[2][0])
423 decomp
->quaternion
[1] = -decomp
->quaternion
[1];
424 if (row
[1][0] > row
[0][1])
425 decomp
->quaternion
[2] = -decomp
->quaternion
[2];
430 // Taken from http://www.w3.org/TR/css3-transforms/.
431 Transform
ComposeTransform(const DecomposedTransform
& decomp
) {
432 SkMatrix44 perspective
= BuildPerspectiveMatrix(decomp
);
433 SkMatrix44 translation
= BuildTranslationMatrix(decomp
);
434 SkMatrix44 rotation
= BuildRotationMatrix(decomp
);
435 SkMatrix44 skew
= BuildSkewMatrix(decomp
);
436 SkMatrix44 scale
= BuildScaleMatrix(decomp
);
438 return ComposeTransform(perspective
, translation
, rotation
, skew
, scale
);
441 bool SnapTransform(Transform
* out
,
442 const Transform
& transform
,
443 const Rect
& viewport
) {
444 DecomposedTransform decomp
;
445 DecomposeTransform(&decomp
, transform
);
447 SkMatrix44 rotation_matrix
= BuildSnappedRotationMatrix(decomp
);
448 SkMatrix44 translation
= BuildSnappedTranslationMatrix(decomp
);
449 SkMatrix44 scale
= BuildSnappedScaleMatrix(decomp
);
451 // Rebuild matrices for other unchanged components.
452 SkMatrix44 perspective
= BuildPerspectiveMatrix(decomp
);
454 // Completely ignore the skew.
455 SkMatrix44
skew(SkMatrix44::kIdentity_Constructor
);
459 ComposeTransform(perspective
, translation
, rotation_matrix
, skew
, scale
);
461 // Verify that viewport is not moved unnaturally.
463 CheckTransformsMapsIntViewportWithinOnePixel(viewport
, transform
, snapped
);
470 Transform
TransformAboutPivot(const gfx::Point
& pivot
,
471 const gfx::Transform
& transform
) {
472 gfx::Transform result
;
473 result
.Translate(pivot
.x(), pivot
.y());
474 result
.PreconcatTransform(transform
);
475 result
.Translate(-pivot
.x(), -pivot
.y());
479 std::string
DecomposedTransform::ToString() const {
480 return base::StringPrintf(
481 "translate: %+0.4f %+0.4f %+0.4f\n"
482 "scale: %+0.4f %+0.4f %+0.4f\n"
483 "skew: %+0.4f %+0.4f %+0.4f\n"
484 "perspective: %+0.4f %+0.4f %+0.4f %+0.4f\n"
485 "quaternion: %+0.4f %+0.4f %+0.4f %+0.4f\n",