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"
10 #include "base/logging.h"
11 #include "base/strings/stringprintf.h"
12 #include "ui/gfx/point.h"
13 #include "ui/gfx/point3_f.h"
14 #include "ui/gfx/rect.h"
20 SkMScalar
Length3(SkMScalar v
[3]) {
21 double vd
[3] = {SkMScalarToDouble(v
[0]), SkMScalarToDouble(v
[1]),
22 SkMScalarToDouble(v
[2])};
23 return SkDoubleToMScalar(
24 std::sqrt(vd
[0] * vd
[0] + vd
[1] * vd
[1] + vd
[2] * vd
[2]));
27 void Scale3(SkMScalar v
[3], SkMScalar scale
) {
28 for (int i
= 0; i
< 3; ++i
)
33 SkMScalar
Dot(const SkMScalar
* a
, const SkMScalar
* b
) {
35 for (int i
= 0; i
< n
; ++i
)
37 return SkDoubleToMScalar(total
);
41 void Combine(SkMScalar
* out
,
46 for (int i
= 0; i
< n
; ++i
)
47 out
[i
] = SkDoubleToMScalar(a
[i
] * scale_a
+ b
[i
] * scale_b
);
50 void Cross3(SkMScalar out
[3], SkMScalar a
[3], SkMScalar b
[3]) {
51 SkMScalar x
= a
[1] * b
[2] - a
[2] * b
[1];
52 SkMScalar y
= a
[2] * b
[0] - a
[0] * b
[2];
53 SkMScalar z
= a
[0] * b
[1] - a
[1] * b
[0];
59 SkMScalar
Round(SkMScalar n
) {
60 return SkDoubleToMScalar(std::floor(SkMScalarToDouble(n
) + 0.5));
63 // Taken from http://www.w3.org/TR/css3-transforms/.
64 bool Slerp(SkMScalar out
[4],
65 const SkMScalar q1
[4],
66 const SkMScalar q2
[4],
68 double product
= Dot
<4>(q1
, q2
);
70 // Clamp product to -1.0 <= product <= 1.0.
71 product
= std::min(std::max(product
, -1.0), 1.0);
73 // Interpolate angles along the shortest path. For example, to interpolate
74 // between a 175 degree angle and a 185 degree angle, interpolate along the
75 // 10 degree path from 175 to 185, rather than along the 350 degree path in
76 // the opposite direction. This matches WebKit's implementation but not
77 // the current W3C spec. Fixing the spec to match this approach is discussed
79 // http://lists.w3.org/Archives/Public/www-style/2013May/0131.html
86 const double epsilon
= 1e-5;
87 if (std::abs(product
- 1.0) < epsilon
) {
88 for (int i
= 0; i
< 4; ++i
)
93 double denom
= std::sqrt(1.0 - product
* product
);
94 double theta
= std::acos(product
);
95 double w
= std::sin(progress
* theta
) * (1.0 / denom
);
97 scale1
*= std::cos(progress
* theta
) - product
* w
;
99 Combine
<4>(out
, q1
, q2
, scale1
, scale2
);
104 // Returns false if the matrix cannot be normalized.
105 bool Normalize(SkMatrix44
& m
) {
106 if (m
.get(3, 3) == 0.0)
110 SkMScalar scale
= 1.0 / m
.get(3, 3);
111 for (int i
= 0; i
< 4; i
++)
112 for (int j
= 0; j
< 4; j
++)
113 m
.set(i
, j
, m
.get(i
, j
) * scale
);
118 SkMatrix44
BuildPerspectiveMatrix(const DecomposedTransform
& decomp
) {
119 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
121 for (int i
= 0; i
< 4; i
++)
122 matrix
.setDouble(3, i
, decomp
.perspective
[i
]);
126 SkMatrix44
BuildTranslationMatrix(const DecomposedTransform
& decomp
) {
127 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
128 // Implicitly calls matrix.setIdentity()
129 matrix
.setTranslate(SkDoubleToMScalar(decomp
.translate
[0]),
130 SkDoubleToMScalar(decomp
.translate
[1]),
131 SkDoubleToMScalar(decomp
.translate
[2]));
135 SkMatrix44
BuildSnappedTranslationMatrix(DecomposedTransform decomp
) {
136 decomp
.translate
[0] = Round(decomp
.translate
[0]);
137 decomp
.translate
[1] = Round(decomp
.translate
[1]);
138 decomp
.translate
[2] = Round(decomp
.translate
[2]);
139 return BuildTranslationMatrix(decomp
);
142 SkMatrix44
BuildRotationMatrix(const DecomposedTransform
& decomp
) {
143 double x
= decomp
.quaternion
[0];
144 double y
= decomp
.quaternion
[1];
145 double z
= decomp
.quaternion
[2];
146 double w
= decomp
.quaternion
[3];
148 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
150 // Implicitly calls matrix.setIdentity()
151 matrix
.set3x3(1.0 - 2.0 * (y
* y
+ z
* z
),
152 2.0 * (x
* y
+ z
* w
),
153 2.0 * (x
* z
- y
* w
),
154 2.0 * (x
* y
- z
* w
),
155 1.0 - 2.0 * (x
* x
+ z
* z
),
156 2.0 * (y
* z
+ x
* w
),
157 2.0 * (x
* z
+ y
* w
),
158 2.0 * (y
* z
- x
* w
),
159 1.0 - 2.0 * (x
* x
+ y
* y
));
163 SkMatrix44
BuildSnappedRotationMatrix(const DecomposedTransform
& decomp
) {
164 // Create snapped rotation.
165 SkMatrix44 rotation_matrix
= BuildRotationMatrix(decomp
);
166 for (int i
= 0; i
< 3; ++i
) {
167 for (int j
= 0; j
< 3; ++j
) {
168 SkMScalar value
= rotation_matrix
.get(i
, j
);
169 // Snap values to -1, 0 or 1.
172 } else if (value
> 0.5f
) {
177 rotation_matrix
.set(i
, j
, value
);
180 return rotation_matrix
;
183 SkMatrix44
BuildSkewMatrix(const DecomposedTransform
& decomp
) {
184 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
186 SkMatrix44
temp(SkMatrix44::kIdentity_Constructor
);
187 if (decomp
.skew
[2]) {
188 temp
.setDouble(1, 2, decomp
.skew
[2]);
189 matrix
.preConcat(temp
);
192 if (decomp
.skew
[1]) {
193 temp
.setDouble(1, 2, 0);
194 temp
.setDouble(0, 2, decomp
.skew
[1]);
195 matrix
.preConcat(temp
);
198 if (decomp
.skew
[0]) {
199 temp
.setDouble(0, 2, 0);
200 temp
.setDouble(0, 1, decomp
.skew
[0]);
201 matrix
.preConcat(temp
);
206 SkMatrix44
BuildScaleMatrix(const DecomposedTransform
& decomp
) {
207 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
208 matrix
.setScale(SkDoubleToMScalar(decomp
.scale
[0]),
209 SkDoubleToMScalar(decomp
.scale
[1]),
210 SkDoubleToMScalar(decomp
.scale
[2]));
214 SkMatrix44
BuildSnappedScaleMatrix(DecomposedTransform decomp
) {
215 decomp
.scale
[0] = Round(decomp
.scale
[0]);
216 decomp
.scale
[1] = Round(decomp
.scale
[1]);
217 decomp
.scale
[2] = Round(decomp
.scale
[2]);
218 return BuildScaleMatrix(decomp
);
221 Transform
ComposeTransform(const SkMatrix44
& perspective
,
222 const SkMatrix44
& translation
,
223 const SkMatrix44
& rotation
,
224 const SkMatrix44
& skew
,
225 const SkMatrix44
& scale
) {
226 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
228 matrix
.preConcat(perspective
);
229 matrix
.preConcat(translation
);
230 matrix
.preConcat(rotation
);
231 matrix
.preConcat(skew
);
232 matrix
.preConcat(scale
);
235 to_return
.matrix() = matrix
;
239 bool CheckViewportPointMapsWithinOnePixel(const Point
& point
,
240 const Transform
& transform
) {
241 Point3F
point_original(point
);
242 Point3F
point_transformed(point
);
244 // Can't use TransformRect here since it would give us the axis-aligned
245 // bounding rect of the 4 points in the initial rectable which is not what we
247 transform
.TransformPoint(&point_transformed
);
249 if ((point_transformed
- point_original
).Length() > 1.f
) {
250 // The changed distance should not be more than 1 pixel.
256 bool CheckTransformsMapsIntViewportWithinOnePixel(const Rect
& viewport
,
257 const Transform
& original
,
258 const Transform
& snapped
) {
260 Transform
original_inv(Transform::kSkipInitialization
);
261 bool invertible
= true;
262 invertible
&= original
.GetInverse(&original_inv
);
263 DCHECK(invertible
) << "Non-invertible transform, cannot snap.";
265 Transform combined
= snapped
* original_inv
;
267 return CheckViewportPointMapsWithinOnePixel(viewport
.origin(), combined
) &&
268 CheckViewportPointMapsWithinOnePixel(viewport
.top_right(), combined
) &&
269 CheckViewportPointMapsWithinOnePixel(viewport
.bottom_left(),
271 CheckViewportPointMapsWithinOnePixel(viewport
.bottom_right(),
277 Transform
GetScaleTransform(const Point
& anchor
, float scale
) {
279 transform
.Translate(anchor
.x() * (1 - scale
),
280 anchor
.y() * (1 - scale
));
281 transform
.Scale(scale
, scale
);
285 DecomposedTransform::DecomposedTransform() {
286 translate
[0] = translate
[1] = translate
[2] = 0.0;
287 scale
[0] = scale
[1] = scale
[2] = 1.0;
288 skew
[0] = skew
[1] = skew
[2] = 0.0;
289 perspective
[0] = perspective
[1] = perspective
[2] = 0.0;
290 quaternion
[0] = quaternion
[1] = quaternion
[2] = 0.0;
291 perspective
[3] = quaternion
[3] = 1.0;
294 bool BlendDecomposedTransforms(DecomposedTransform
* out
,
295 const DecomposedTransform
& to
,
296 const DecomposedTransform
& from
,
298 double scalea
= progress
;
299 double scaleb
= 1.0 - progress
;
300 Combine
<3>(out
->translate
, to
.translate
, from
.translate
, scalea
, scaleb
);
301 Combine
<3>(out
->scale
, to
.scale
, from
.scale
, scalea
, scaleb
);
302 Combine
<3>(out
->skew
, to
.skew
, from
.skew
, scalea
, scaleb
);
304 out
->perspective
, to
.perspective
, from
.perspective
, scalea
, scaleb
);
305 return Slerp(out
->quaternion
, from
.quaternion
, to
.quaternion
, progress
);
308 // Taken from http://www.w3.org/TR/css3-transforms/.
309 bool DecomposeTransform(DecomposedTransform
* decomp
,
310 const Transform
& transform
) {
314 // We'll operate on a copy of the matrix.
315 SkMatrix44 matrix
= transform
.matrix();
317 // If we cannot normalize the matrix, then bail early as we cannot decompose.
318 if (!Normalize(matrix
))
321 SkMatrix44 perspectiveMatrix
= matrix
;
323 for (int i
= 0; i
< 3; ++i
)
324 perspectiveMatrix
.set(3, i
, 0.0);
326 perspectiveMatrix
.set(3, 3, 1.0);
328 // If the perspective matrix is not invertible, we are also unable to
329 // decompose, so we'll bail early. Constant taken from SkMatrix44::invert.
330 if (std::abs(perspectiveMatrix
.determinant()) < 1e-8)
333 if (matrix
.get(3, 0) != 0.0 || matrix
.get(3, 1) != 0.0 ||
334 matrix
.get(3, 2) != 0.0) {
335 // rhs is the right hand side of the equation.
343 // Solve the equation by inverting perspectiveMatrix and multiplying
344 // rhs by the inverse.
345 SkMatrix44
inversePerspectiveMatrix(SkMatrix44::kUninitialized_Constructor
);
346 if (!perspectiveMatrix
.invert(&inversePerspectiveMatrix
))
349 SkMatrix44 transposedInversePerspectiveMatrix
=
350 inversePerspectiveMatrix
;
352 transposedInversePerspectiveMatrix
.transpose();
353 transposedInversePerspectiveMatrix
.mapMScalars(rhs
);
355 for (int i
= 0; i
< 4; ++i
)
356 decomp
->perspective
[i
] = rhs
[i
];
360 for (int i
= 0; i
< 3; ++i
)
361 decomp
->perspective
[i
] = 0.0;
362 decomp
->perspective
[3] = 1.0;
365 for (int i
= 0; i
< 3; i
++)
366 decomp
->translate
[i
] = matrix
.get(i
, 3);
369 for (int i
= 0; i
< 3; i
++)
370 for (int j
= 0; j
< 3; ++j
)
371 row
[i
][j
] = matrix
.get(j
, i
);
373 // Compute X scale factor and normalize first row.
374 decomp
->scale
[0] = Length3(row
[0]);
375 if (decomp
->scale
[0] != 0.0)
376 Scale3(row
[0], 1.0 / decomp
->scale
[0]);
378 // Compute XY shear factor and make 2nd row orthogonal to 1st.
379 decomp
->skew
[0] = Dot
<3>(row
[0], row
[1]);
380 Combine
<3>(row
[1], row
[1], row
[0], 1.0, -decomp
->skew
[0]);
382 // Now, compute Y scale and normalize 2nd row.
383 decomp
->scale
[1] = Length3(row
[1]);
384 if (decomp
->scale
[1] != 0.0)
385 Scale3(row
[1], 1.0 / decomp
->scale
[1]);
387 decomp
->skew
[0] /= decomp
->scale
[1];
389 // Compute XZ and YZ shears, orthogonalize 3rd row
390 decomp
->skew
[1] = Dot
<3>(row
[0], row
[2]);
391 Combine
<3>(row
[2], row
[2], row
[0], 1.0, -decomp
->skew
[1]);
392 decomp
->skew
[2] = Dot
<3>(row
[1], row
[2]);
393 Combine
<3>(row
[2], row
[2], row
[1], 1.0, -decomp
->skew
[2]);
395 // Next, get Z scale and normalize 3rd row.
396 decomp
->scale
[2] = Length3(row
[2]);
397 if (decomp
->scale
[2] != 0.0)
398 Scale3(row
[2], 1.0 / decomp
->scale
[2]);
400 decomp
->skew
[1] /= decomp
->scale
[2];
401 decomp
->skew
[2] /= decomp
->scale
[2];
403 // At this point, the matrix (in rows) is orthonormal.
404 // Check for a coordinate system flip. If the determinant
405 // is -1, then negate the matrix and the scaling factors.
407 Cross3(pdum3
, row
[1], row
[2]);
408 if (Dot
<3>(row
[0], pdum3
) < 0) {
409 for (int i
= 0; i
< 3; i
++) {
410 decomp
->scale
[i
] *= -1.0;
411 for (int j
= 0; j
< 3; ++j
)
416 decomp
->quaternion
[0] =
417 0.5 * std::sqrt(std::max(1.0 + row
[0][0] - row
[1][1] - row
[2][2], 0.0));
418 decomp
->quaternion
[1] =
419 0.5 * std::sqrt(std::max(1.0 - row
[0][0] + row
[1][1] - row
[2][2], 0.0));
420 decomp
->quaternion
[2] =
421 0.5 * std::sqrt(std::max(1.0 - row
[0][0] - row
[1][1] + row
[2][2], 0.0));
422 decomp
->quaternion
[3] =
423 0.5 * std::sqrt(std::max(1.0 + row
[0][0] + row
[1][1] + row
[2][2], 0.0));
425 if (row
[2][1] > row
[1][2])
426 decomp
->quaternion
[0] = -decomp
->quaternion
[0];
427 if (row
[0][2] > row
[2][0])
428 decomp
->quaternion
[1] = -decomp
->quaternion
[1];
429 if (row
[1][0] > row
[0][1])
430 decomp
->quaternion
[2] = -decomp
->quaternion
[2];
435 // Taken from http://www.w3.org/TR/css3-transforms/.
436 Transform
ComposeTransform(const DecomposedTransform
& decomp
) {
437 SkMatrix44 perspective
= BuildPerspectiveMatrix(decomp
);
438 SkMatrix44 translation
= BuildTranslationMatrix(decomp
);
439 SkMatrix44 rotation
= BuildRotationMatrix(decomp
);
440 SkMatrix44 skew
= BuildSkewMatrix(decomp
);
441 SkMatrix44 scale
= BuildScaleMatrix(decomp
);
443 return ComposeTransform(perspective
, translation
, rotation
, skew
, scale
);
446 bool SnapTransform(Transform
* out
,
447 const Transform
& transform
,
448 const Rect
& viewport
) {
449 DecomposedTransform decomp
;
450 DecomposeTransform(&decomp
, transform
);
452 SkMatrix44 rotation_matrix
= BuildSnappedRotationMatrix(decomp
);
453 SkMatrix44 translation
= BuildSnappedTranslationMatrix(decomp
);
454 SkMatrix44 scale
= BuildSnappedScaleMatrix(decomp
);
456 // Rebuild matrices for other unchanged components.
457 SkMatrix44 perspective
= BuildPerspectiveMatrix(decomp
);
459 // Completely ignore the skew.
460 SkMatrix44
skew(SkMatrix44::kIdentity_Constructor
);
464 ComposeTransform(perspective
, translation
, rotation_matrix
, skew
, scale
);
466 // Verify that viewport is not moved unnaturally.
468 CheckTransformsMapsIntViewportWithinOnePixel(viewport
, transform
, snapped
);
475 std::string
DecomposedTransform::ToString() const {
476 return base::StringPrintf(
477 "translate: %+0.4f %+0.4f %+0.4f\n"
478 "scale: %+0.4f %+0.4f %+0.4f\n"
479 "skew: %+0.4f %+0.4f %+0.4f\n"
480 "perspective: %+0.4f %+0.4f %+0.4f %+0.4f\n"
481 "quaternion: %+0.4f %+0.4f %+0.4f %+0.4f\n",