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/point.h"
14 #include "ui/gfx/point3_f.h"
15 #include "ui/gfx/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 // Interpolate angles along the shortest path. For example, to interpolate
70 // between a 175 degree angle and a 185 degree angle, interpolate along the
71 // 10 degree path from 175 to 185, rather than along the 350 degree path in
72 // the opposite direction. This matches WebKit's implementation but not
73 // the current W3C spec. Fixing the spec to match this approach is discussed
75 // http://lists.w3.org/Archives/Public/www-style/2013May/0131.html
82 const double epsilon
= 1e-5;
83 if (std::abs(product
- 1.0) < epsilon
) {
84 for (int i
= 0; i
< 4; ++i
)
89 double denom
= std::sqrt(1.0 - product
* product
);
90 double theta
= std::acos(product
);
91 double w
= std::sin(progress
* theta
) * (1.0 / denom
);
93 scale1
*= std::cos(progress
* theta
) - product
* w
;
95 Combine
<4>(out
, q1
, q2
, scale1
, scale2
);
100 // Returns false if the matrix cannot be normalized.
101 bool Normalize(SkMatrix44
& m
) {
102 if (m
.get(3, 3) == 0.0)
106 SkMScalar scale
= SK_MScalar1
/ m
.get(3, 3);
107 for (int i
= 0; i
< 4; i
++)
108 for (int j
= 0; j
< 4; j
++)
109 m
.set(i
, j
, m
.get(i
, j
) * scale
);
114 SkMatrix44
BuildPerspectiveMatrix(const DecomposedTransform
& decomp
) {
115 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
117 for (int i
= 0; i
< 4; i
++)
118 matrix
.setDouble(3, i
, decomp
.perspective
[i
]);
122 SkMatrix44
BuildTranslationMatrix(const DecomposedTransform
& decomp
) {
123 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
124 // Implicitly calls matrix.setIdentity()
125 matrix
.setTranslate(SkDoubleToMScalar(decomp
.translate
[0]),
126 SkDoubleToMScalar(decomp
.translate
[1]),
127 SkDoubleToMScalar(decomp
.translate
[2]));
131 SkMatrix44
BuildSnappedTranslationMatrix(DecomposedTransform decomp
) {
132 decomp
.translate
[0] = Round(decomp
.translate
[0]);
133 decomp
.translate
[1] = Round(decomp
.translate
[1]);
134 decomp
.translate
[2] = Round(decomp
.translate
[2]);
135 return BuildTranslationMatrix(decomp
);
138 SkMatrix44
BuildRotationMatrix(const DecomposedTransform
& decomp
) {
139 double x
= decomp
.quaternion
[0];
140 double y
= decomp
.quaternion
[1];
141 double z
= decomp
.quaternion
[2];
142 double w
= decomp
.quaternion
[3];
144 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
146 // Implicitly calls matrix.setIdentity()
147 matrix
.set3x3(SkDoubleToMScalar(1.0 - 2.0 * (y
* y
+ z
* z
)),
148 SkDoubleToMScalar(2.0 * (x
* y
+ z
* w
)),
149 SkDoubleToMScalar(2.0 * (x
* z
- y
* w
)),
150 SkDoubleToMScalar(2.0 * (x
* y
- z
* w
)),
151 SkDoubleToMScalar(1.0 - 2.0 * (x
* x
+ z
* z
)),
152 SkDoubleToMScalar(2.0 * (y
* z
+ x
* w
)),
153 SkDoubleToMScalar(2.0 * (x
* z
+ y
* w
)),
154 SkDoubleToMScalar(2.0 * (y
* z
- x
* w
)),
155 SkDoubleToMScalar(1.0 - 2.0 * (x
* x
+ y
* y
)));
159 SkMatrix44
BuildSnappedRotationMatrix(const DecomposedTransform
& decomp
) {
160 // Create snapped rotation.
161 SkMatrix44 rotation_matrix
= BuildRotationMatrix(decomp
);
162 for (int i
= 0; i
< 3; ++i
) {
163 for (int j
= 0; j
< 3; ++j
) {
164 SkMScalar value
= rotation_matrix
.get(i
, j
);
165 // Snap values to -1, 0 or 1.
168 } else if (value
> 0.5f
) {
173 rotation_matrix
.set(i
, j
, value
);
176 return rotation_matrix
;
179 SkMatrix44
BuildSkewMatrix(const DecomposedTransform
& decomp
) {
180 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
182 SkMatrix44
temp(SkMatrix44::kIdentity_Constructor
);
183 if (decomp
.skew
[2]) {
184 temp
.setDouble(1, 2, decomp
.skew
[2]);
185 matrix
.preConcat(temp
);
188 if (decomp
.skew
[1]) {
189 temp
.setDouble(1, 2, 0);
190 temp
.setDouble(0, 2, decomp
.skew
[1]);
191 matrix
.preConcat(temp
);
194 if (decomp
.skew
[0]) {
195 temp
.setDouble(0, 2, 0);
196 temp
.setDouble(0, 1, decomp
.skew
[0]);
197 matrix
.preConcat(temp
);
202 SkMatrix44
BuildScaleMatrix(const DecomposedTransform
& decomp
) {
203 SkMatrix44
matrix(SkMatrix44::kUninitialized_Constructor
);
204 matrix
.setScale(SkDoubleToMScalar(decomp
.scale
[0]),
205 SkDoubleToMScalar(decomp
.scale
[1]),
206 SkDoubleToMScalar(decomp
.scale
[2]));
210 SkMatrix44
BuildSnappedScaleMatrix(DecomposedTransform decomp
) {
211 decomp
.scale
[0] = Round(decomp
.scale
[0]);
212 decomp
.scale
[1] = Round(decomp
.scale
[1]);
213 decomp
.scale
[2] = Round(decomp
.scale
[2]);
214 return BuildScaleMatrix(decomp
);
217 Transform
ComposeTransform(const SkMatrix44
& perspective
,
218 const SkMatrix44
& translation
,
219 const SkMatrix44
& rotation
,
220 const SkMatrix44
& skew
,
221 const SkMatrix44
& scale
) {
222 SkMatrix44
matrix(SkMatrix44::kIdentity_Constructor
);
224 matrix
.preConcat(perspective
);
225 matrix
.preConcat(translation
);
226 matrix
.preConcat(rotation
);
227 matrix
.preConcat(skew
);
228 matrix
.preConcat(scale
);
231 to_return
.matrix() = matrix
;
235 bool CheckViewportPointMapsWithinOnePixel(const Point
& point
,
236 const Transform
& transform
) {
237 Point3F
point_original(point
);
238 Point3F
point_transformed(point
);
240 // Can't use TransformRect here since it would give us the axis-aligned
241 // bounding rect of the 4 points in the initial rectable which is not what we
243 transform
.TransformPoint(&point_transformed
);
245 if ((point_transformed
- point_original
).Length() > 1.f
) {
246 // The changed distance should not be more than 1 pixel.
252 bool CheckTransformsMapsIntViewportWithinOnePixel(const Rect
& viewport
,
253 const Transform
& original
,
254 const Transform
& snapped
) {
256 Transform
original_inv(Transform::kSkipInitialization
);
257 bool invertible
= true;
258 invertible
&= original
.GetInverse(&original_inv
);
259 DCHECK(invertible
) << "Non-invertible transform, cannot snap.";
261 Transform combined
= snapped
* original_inv
;
263 return CheckViewportPointMapsWithinOnePixel(viewport
.origin(), combined
) &&
264 CheckViewportPointMapsWithinOnePixel(viewport
.top_right(), combined
) &&
265 CheckViewportPointMapsWithinOnePixel(viewport
.bottom_left(),
267 CheckViewportPointMapsWithinOnePixel(viewport
.bottom_right(),
273 Transform
GetScaleTransform(const Point
& anchor
, float scale
) {
275 transform
.Translate(anchor
.x() * (1 - scale
),
276 anchor
.y() * (1 - scale
));
277 transform
.Scale(scale
, scale
);
281 DecomposedTransform::DecomposedTransform() {
282 translate
[0] = translate
[1] = translate
[2] = 0.0;
283 scale
[0] = scale
[1] = scale
[2] = 1.0;
284 skew
[0] = skew
[1] = skew
[2] = 0.0;
285 perspective
[0] = perspective
[1] = perspective
[2] = 0.0;
286 quaternion
[0] = quaternion
[1] = quaternion
[2] = 0.0;
287 perspective
[3] = quaternion
[3] = 1.0;
290 bool BlendDecomposedTransforms(DecomposedTransform
* out
,
291 const DecomposedTransform
& to
,
292 const DecomposedTransform
& from
,
294 double scalea
= progress
;
295 double scaleb
= 1.0 - progress
;
296 Combine
<3>(out
->translate
, to
.translate
, from
.translate
, scalea
, scaleb
);
297 Combine
<3>(out
->scale
, to
.scale
, from
.scale
, scalea
, scaleb
);
298 Combine
<3>(out
->skew
, to
.skew
, from
.skew
, scalea
, scaleb
);
300 out
->perspective
, to
.perspective
, from
.perspective
, scalea
, scaleb
);
301 return Slerp(out
->quaternion
, from
.quaternion
, to
.quaternion
, progress
);
304 // Taken from http://www.w3.org/TR/css3-transforms/.
305 bool DecomposeTransform(DecomposedTransform
* decomp
,
306 const Transform
& transform
) {
310 // We'll operate on a copy of the matrix.
311 SkMatrix44 matrix
= transform
.matrix();
313 // If we cannot normalize the matrix, then bail early as we cannot decompose.
314 if (!Normalize(matrix
))
317 SkMatrix44 perspectiveMatrix
= matrix
;
319 for (int i
= 0; i
< 3; ++i
)
320 perspectiveMatrix
.set(3, i
, 0.0);
322 perspectiveMatrix
.set(3, 3, 1.0);
324 // If the perspective matrix is not invertible, we are also unable to
325 // decompose, so we'll bail early. Constant taken from SkMatrix44::invert.
326 if (std::abs(perspectiveMatrix
.determinant()) < 1e-8)
329 if (matrix
.get(3, 0) != 0.0 || matrix
.get(3, 1) != 0.0 ||
330 matrix
.get(3, 2) != 0.0) {
331 // rhs is the right hand side of the equation.
339 // Solve the equation by inverting perspectiveMatrix and multiplying
340 // rhs by the inverse.
341 SkMatrix44
inversePerspectiveMatrix(SkMatrix44::kUninitialized_Constructor
);
342 if (!perspectiveMatrix
.invert(&inversePerspectiveMatrix
))
345 SkMatrix44 transposedInversePerspectiveMatrix
=
346 inversePerspectiveMatrix
;
348 transposedInversePerspectiveMatrix
.transpose();
349 transposedInversePerspectiveMatrix
.mapMScalars(rhs
);
351 for (int i
= 0; i
< 4; ++i
)
352 decomp
->perspective
[i
] = rhs
[i
];
356 for (int i
= 0; i
< 3; ++i
)
357 decomp
->perspective
[i
] = 0.0;
358 decomp
->perspective
[3] = 1.0;
361 for (int i
= 0; i
< 3; i
++)
362 decomp
->translate
[i
] = matrix
.get(i
, 3);
365 for (int i
= 0; i
< 3; i
++)
366 for (int j
= 0; j
< 3; ++j
)
367 row
[i
][j
] = matrix
.get(j
, i
);
369 // Compute X scale factor and normalize first row.
370 decomp
->scale
[0] = Length3(row
[0]);
371 if (decomp
->scale
[0] != 0.0) {
372 row
[0][0] /= decomp
->scale
[0];
373 row
[0][1] /= decomp
->scale
[0];
374 row
[0][2] /= decomp
->scale
[0];
377 // Compute XY shear factor and make 2nd row orthogonal to 1st.
378 decomp
->skew
[0] = Dot
<3>(row
[0], row
[1]);
379 Combine
<3>(row
[1], row
[1], row
[0], 1.0, -decomp
->skew
[0]);
381 // Now, compute Y scale and normalize 2nd row.
382 decomp
->scale
[1] = Length3(row
[1]);
383 if (decomp
->scale
[1] != 0.0) {
384 row
[1][0] /= decomp
->scale
[1];
385 row
[1][1] /= decomp
->scale
[1];
386 row
[1][2] /= decomp
->scale
[1];
389 decomp
->skew
[0] /= decomp
->scale
[1];
391 // Compute XZ and YZ shears, orthogonalize 3rd row
392 decomp
->skew
[1] = Dot
<3>(row
[0], row
[2]);
393 Combine
<3>(row
[2], row
[2], row
[0], 1.0, -decomp
->skew
[1]);
394 decomp
->skew
[2] = Dot
<3>(row
[1], row
[2]);
395 Combine
<3>(row
[2], row
[2], row
[1], 1.0, -decomp
->skew
[2]);
397 // Next, get Z scale and normalize 3rd row.
398 decomp
->scale
[2] = Length3(row
[2]);
399 if (decomp
->scale
[2] != 0.0) {
400 row
[2][0] /= decomp
->scale
[2];
401 row
[2][1] /= decomp
->scale
[2];
402 row
[2][2] /= decomp
->scale
[2];
405 decomp
->skew
[1] /= decomp
->scale
[2];
406 decomp
->skew
[2] /= decomp
->scale
[2];
408 // At this point, the matrix (in rows) is orthonormal.
409 // Check for a coordinate system flip. If the determinant
410 // is -1, then negate the matrix and the scaling factors.
412 Cross3(pdum3
, row
[1], row
[2]);
413 if (Dot
<3>(row
[0], pdum3
) < 0) {
414 for (int i
= 0; i
< 3; i
++) {
415 decomp
->scale
[i
] *= -1.0;
416 for (int j
= 0; j
< 3; ++j
)
421 double row00
= SkMScalarToDouble(row
[0][0]);
422 double row11
= SkMScalarToDouble(row
[1][1]);
423 double row22
= SkMScalarToDouble(row
[2][2]);
424 decomp
->quaternion
[0] = SkDoubleToMScalar(
425 0.5 * std::sqrt(std::max(1.0 + row00
- row11
- row22
, 0.0)));
426 decomp
->quaternion
[1] = SkDoubleToMScalar(
427 0.5 * std::sqrt(std::max(1.0 - row00
+ row11
- row22
, 0.0)));
428 decomp
->quaternion
[2] = SkDoubleToMScalar(
429 0.5 * std::sqrt(std::max(1.0 - row00
- row11
+ row22
, 0.0)));
430 decomp
->quaternion
[3] = SkDoubleToMScalar(
431 0.5 * std::sqrt(std::max(1.0 + row00
+ row11
+ row22
, 0.0)));
433 if (row
[2][1] > row
[1][2])
434 decomp
->quaternion
[0] = -decomp
->quaternion
[0];
435 if (row
[0][2] > row
[2][0])
436 decomp
->quaternion
[1] = -decomp
->quaternion
[1];
437 if (row
[1][0] > row
[0][1])
438 decomp
->quaternion
[2] = -decomp
->quaternion
[2];
443 // Taken from http://www.w3.org/TR/css3-transforms/.
444 Transform
ComposeTransform(const DecomposedTransform
& decomp
) {
445 SkMatrix44 perspective
= BuildPerspectiveMatrix(decomp
);
446 SkMatrix44 translation
= BuildTranslationMatrix(decomp
);
447 SkMatrix44 rotation
= BuildRotationMatrix(decomp
);
448 SkMatrix44 skew
= BuildSkewMatrix(decomp
);
449 SkMatrix44 scale
= BuildScaleMatrix(decomp
);
451 return ComposeTransform(perspective
, translation
, rotation
, skew
, scale
);
454 bool SnapTransform(Transform
* out
,
455 const Transform
& transform
,
456 const Rect
& viewport
) {
457 DecomposedTransform decomp
;
458 DecomposeTransform(&decomp
, transform
);
460 SkMatrix44 rotation_matrix
= BuildSnappedRotationMatrix(decomp
);
461 SkMatrix44 translation
= BuildSnappedTranslationMatrix(decomp
);
462 SkMatrix44 scale
= BuildSnappedScaleMatrix(decomp
);
464 // Rebuild matrices for other unchanged components.
465 SkMatrix44 perspective
= BuildPerspectiveMatrix(decomp
);
467 // Completely ignore the skew.
468 SkMatrix44
skew(SkMatrix44::kIdentity_Constructor
);
472 ComposeTransform(perspective
, translation
, rotation_matrix
, skew
, scale
);
474 // Verify that viewport is not moved unnaturally.
476 CheckTransformsMapsIntViewportWithinOnePixel(viewport
, transform
, snapped
);
483 Transform
TransformAboutPivot(const gfx::Point
& pivot
,
484 const gfx::Transform
& transform
) {
485 gfx::Transform result
;
486 result
.Translate(pivot
.x(), pivot
.y());
487 result
.PreconcatTransform(transform
);
488 result
.Translate(-pivot
.x(), -pivot
.y());
492 std::string
DecomposedTransform::ToString() const {
493 return base::StringPrintf(
494 "translate: %+0.4f %+0.4f %+0.4f\n"
495 "scale: %+0.4f %+0.4f %+0.4f\n"
496 "skew: %+0.4f %+0.4f %+0.4f\n"
497 "perspective: %+0.4f %+0.4f %+0.4f %+0.4f\n"
498 "quaternion: %+0.4f %+0.4f %+0.4f %+0.4f\n",