transform_util_unittest.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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.
  4. #include "ui/gfx/geometry/transform_util.h"
  5. #include <stddef.h>
  6. #include <limits>
  7. #include "base/cxx17_backports.h"
  8. #include "base/numerics/math_constants.h"
  9. #include "testing/gtest/include/gtest/gtest.h"
  10. #include "ui/gfx/geometry/point.h"
  11. #include "ui/gfx/geometry/point3_f.h"
  12. #include "ui/gfx/geometry/rect.h"
  13. #include "ui/gfx/geometry/rect_f.h"
  14. namespace gfx {
  15. namespace {
  16. #define EXPECT_APPROX_EQ(val1, val2) EXPECT_NEAR(val1, val2, 1e-6);
  17. TEST(TransformUtilTest, GetScaleTransform) {
  18. const Point kAnchor(20, 40);
  19. const float kScale = 0.5f;
  20. Transform scale = GetScaleTransform(kAnchor, kScale);
  21. const int kOffset = 10;
  22. for (int sign_x = -1; sign_x <= 1; ++sign_x) {
  23. for (int sign_y = -1; sign_y <= 1; ++sign_y) {
  24. Point test(kAnchor.x() + sign_x * kOffset,
  25. kAnchor.y() + sign_y * kOffset);
  26. scale.TransformPoint(&test);
  27. EXPECT_EQ(Point(kAnchor.x() + sign_x * kOffset * kScale,
  28. kAnchor.y() + sign_y * kOffset * kScale),
  29. test);
  30. }
  31. }
  32. }
  33. TEST(TransformUtilTest, SnapRotation) {
  34. Transform result(Transform::kSkipInitialization);
  35. Transform transform;
  36. transform.RotateAboutZAxis(89.99);
  37. Rect viewport(1920, 1200);
  38. bool snapped = SnapTransform(&result, transform, viewport);
  39. EXPECT_TRUE(snapped) << "Viewport should snap for this rotation.";
  40. }
  41. TEST(TransformUtilTest, SnapRotationDistantViewport) {
  42. const int kOffset = 5000;
  43. Transform result(Transform::kSkipInitialization);
  44. Transform transform;
  45. transform.RotateAboutZAxis(89.99);
  46. Rect viewport(kOffset, kOffset, 1920, 1200);
  47. bool snapped = SnapTransform(&result, transform, viewport);
  48. EXPECT_FALSE(snapped) << "Distant viewport shouldn't snap by more than 1px.";
  49. }
  50. TEST(TransformUtilTest, NoSnapRotation) {
  51. Transform result(Transform::kSkipInitialization);
  52. Transform transform;
  53. const int kOffset = 5000;
  54. transform.RotateAboutZAxis(89.9);
  55. Rect viewport(kOffset, kOffset, 1920, 1200);
  56. bool snapped = SnapTransform(&result, transform, viewport);
  57. EXPECT_FALSE(snapped) << "Viewport should not snap for this rotation.";
  58. }
  59. // Translations should always be snappable, the most we would move is 0.5
  60. // pixels towards either direction to the nearest value in each component.
  61. TEST(TransformUtilTest, SnapTranslation) {
  62. Transform result(Transform::kSkipInitialization);
  63. Transform transform;
  64. transform.Translate3d(SkDoubleToScalar(1.01), SkDoubleToScalar(1.99),
  65. SkDoubleToScalar(3.0));
  66. Rect viewport(1920, 1200);
  67. bool snapped = SnapTransform(&result, transform, viewport);
  68. EXPECT_TRUE(snapped) << "Viewport should snap for this translation.";
  69. }
  70. TEST(TransformUtilTest, SnapTranslationDistantViewport) {
  71. Transform result(Transform::kSkipInitialization);
  72. Transform transform;
  73. const int kOffset = 5000;
  74. transform.Translate3d(SkDoubleToScalar(1.01), SkDoubleToScalar(1.99),
  75. SkDoubleToScalar(3.0));
  76. Rect viewport(kOffset, kOffset, 1920, 1200);
  77. bool snapped = SnapTransform(&result, transform, viewport);
  78. EXPECT_TRUE(snapped)
  79. << "Distant viewport should still snap by less than 1px.";
  80. }
  81. TEST(TransformUtilTest, SnapScale) {
  82. Transform result(Transform::kSkipInitialization);
  83. Transform transform;
  84. transform.Scale3d(SkDoubleToScalar(5.0), SkDoubleToScalar(2.00001),
  85. SkDoubleToScalar(1.0));
  86. Rect viewport(1920, 1200);
  87. bool snapped = SnapTransform(&result, transform, viewport);
  88. EXPECT_TRUE(snapped) << "Viewport should snap for this scaling.";
  89. }
  90. TEST(TransformUtilTest, NoSnapScale) {
  91. Transform result(Transform::kSkipInitialization);
  92. Transform transform;
  93. transform.Scale3d(SkDoubleToScalar(5.0), SkDoubleToScalar(2.1),
  94. SkDoubleToScalar(1.0));
  95. Rect viewport(1920, 1200);
  96. bool snapped = SnapTransform(&result, transform, viewport);
  97. EXPECT_FALSE(snapped) << "Viewport shouldn't snap for this scaling.";
  98. }
  99. TEST(TransformUtilTest, SnapCompositeTransform) {
  100. Transform result(Transform::kSkipInitialization);
  101. Transform transform;
  102. transform.Translate3d(SkDoubleToScalar(30.5), SkDoubleToScalar(20.0),
  103. SkDoubleToScalar(10.1));
  104. transform.RotateAboutZAxis(89.99);
  105. transform.Scale3d(SkDoubleToScalar(1.0), SkDoubleToScalar(3.00001),
  106. SkDoubleToScalar(2.0));
  107. Rect viewport(1920, 1200);
  108. bool snapped = SnapTransform(&result, transform, viewport);
  109. ASSERT_TRUE(snapped) << "Viewport should snap all components.";
  110. Point3F point;
  111. point = Point3F(PointF(viewport.origin()));
  112. result.TransformPoint(&point);
  113. EXPECT_EQ(Point3F(31.f, 20.f, 10.f), point) << "Transformed origin";
  114. point = Point3F(PointF(viewport.top_right()));
  115. result.TransformPoint(&point);
  116. EXPECT_EQ(Point3F(31.f, 1940.f, 10.f), point) << "Transformed top-right";
  117. point = Point3F(PointF(viewport.bottom_left()));
  118. result.TransformPoint(&point);
  119. EXPECT_EQ(Point3F(-3569.f, 20.f, 10.f), point) << "Transformed bottom-left";
  120. point = Point3F(PointF(viewport.bottom_right()));
  121. result.TransformPoint(&point);
  122. EXPECT_EQ(Point3F(-3569.f, 1940.f, 10.f), point)
  123. << "Transformed bottom-right";
  124. }
  125. TEST(TransformUtilTest, NoSnapSkewedCompositeTransform) {
  126. Transform result(Transform::kSkipInitialization);
  127. Transform transform;
  128. transform.RotateAboutZAxis(89.99);
  129. transform.Scale3d(SkDoubleToScalar(1.0), SkDoubleToScalar(3.00001),
  130. SkDoubleToScalar(2.0));
  131. transform.Translate3d(SkDoubleToScalar(30.5), SkDoubleToScalar(20.0),
  132. SkDoubleToScalar(10.1));
  133. transform.Skew(20.0, 0.0);
  134. Rect viewport(1920, 1200);
  135. bool snapped = SnapTransform(&result, transform, viewport);
  136. EXPECT_FALSE(snapped) << "Skewed viewport should not snap.";
  137. }
  138. TEST(TransformUtilTest, TransformAboutPivot) {
  139. Transform transform;
  140. transform.Scale(3, 4);
  141. transform = TransformAboutPivot(Point(7, 8), transform);
  142. Point point;
  143. point = Point(0, 0);
  144. transform.TransformPoint(&point);
  145. EXPECT_EQ(Point(-14, -24).ToString(), point.ToString());
  146. point = Point(1, 1);
  147. transform.TransformPoint(&point);
  148. EXPECT_EQ(Point(-11, -20).ToString(), point.ToString());
  149. }
  150. TEST(TransformUtilTest, BlendOppositeQuaternions) {
  151. DecomposedTransform first;
  152. DecomposedTransform second;
  153. second.quaternion.set_w(-second.quaternion.w());
  154. DecomposedTransform result = BlendDecomposedTransforms(first, second, 0.25);
  155. EXPECT_TRUE(std::isfinite(result.quaternion.x()));
  156. EXPECT_TRUE(std::isfinite(result.quaternion.y()));
  157. EXPECT_TRUE(std::isfinite(result.quaternion.z()));
  158. EXPECT_TRUE(std::isfinite(result.quaternion.w()));
  159. EXPECT_FALSE(std::isnan(result.quaternion.x()));
  160. EXPECT_FALSE(std::isnan(result.quaternion.y()));
  161. EXPECT_FALSE(std::isnan(result.quaternion.z()));
  162. EXPECT_FALSE(std::isnan(result.quaternion.w()));
  163. }
  164. double ComputeDecompRecompError(const Transform& transform) {
  165. DecomposedTransform decomp;
  166. DecomposeTransform(&decomp, transform);
  167. Transform composed = ComposeTransform(decomp);
  168. float expected[16];
  169. float actual[16];
  170. transform.matrix().getRowMajor(expected);
  171. composed.matrix().getRowMajor(actual);
  172. double sse = 0;
  173. for (int i = 0; i < 16; i++) {
  174. double diff = expected[i] - actual[i];
  175. sse += diff * diff;
  176. }
  177. return sse;
  178. }
  179. TEST(TransformUtilTest, RoundTripTest) {
  180. // rotateZ(90deg)
  181. EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(0, 1, -1, 0, 0, 0)));
  182. // rotateZ(180deg)
  183. // Edge case where w = 0.
  184. EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(-1, 0, 0, -1, 0, 0)));
  185. // rotateX(90deg) rotateY(90deg) rotateZ(90deg)
  186. // [1 0 0][ 0 0 1][0 -1 0] [0 0 1][0 -1 0] [0 0 1]
  187. // [0 0 -1][ 0 1 0][1 0 0] = [1 0 0][1 0 0] = [0 -1 0]
  188. // [0 1 0][-1 0 0][0 0 1] [0 1 0][0 0 1] [1 0 0]
  189. // This test case leads to Gimbal lock when using Euler angles.
  190. EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(
  191. 0, 0, 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1)));
  192. // Quaternion matrices with 0 off-diagonal elements, and negative trace.
  193. // Stress tests handling of degenerate cases in computing quaternions.
  194. // Validates fix for https://crbug.com/647554.
  195. EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(1, 1, 1, 0, 0, 0)));
  196. EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(
  197. -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)));
  198. EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(
  199. 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)));
  200. EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(
  201. 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1)));
  202. }
  203. TEST(TransformUtilTest, Transform2D) {
  204. // The spec covering interpolation of 2D matrix transforms calls for inverting
  205. // one of the axis in the case of a negative determinant. This differs from
  206. // the general 3D spec, which calls for flipping all of the scales when the
  207. // determinant is negative. Flipping all scales not only introduces rotation
  208. // in the case of a trivial scale inversion, but causes transformed objects
  209. // to needlessly shrink and grow as they transform through scale = 0 along
  210. // multiple axes. 2D transformation matrices should follow the 2D spec
  211. // regarding matrix decomposition.
  212. DecomposedTransform decompFlipX;
  213. DecomposeTransform(&decompFlipX, Transform(-1, 0, 0, 1, 0, 0));
  214. EXPECT_APPROX_EQ(-1, decompFlipX.scale[0]);
  215. EXPECT_APPROX_EQ(1, decompFlipX.scale[1]);
  216. EXPECT_APPROX_EQ(1, decompFlipX.scale[2]);
  217. EXPECT_APPROX_EQ(0, decompFlipX.quaternion.z());
  218. EXPECT_APPROX_EQ(1, decompFlipX.quaternion.w());
  219. DecomposedTransform decompFlipY;
  220. DecomposeTransform(&decompFlipY, Transform(1, 0, 0, -1, 0, 0));
  221. EXPECT_APPROX_EQ(1, decompFlipY.scale[0]);
  222. EXPECT_APPROX_EQ(-1, decompFlipY.scale[1]);
  223. EXPECT_APPROX_EQ(1, decompFlipY.scale[2]);
  224. EXPECT_APPROX_EQ(0, decompFlipY.quaternion.z());
  225. EXPECT_APPROX_EQ(1, decompFlipY.quaternion.w());
  226. DecomposedTransform decompR180;
  227. DecomposeTransform(&decompR180, Transform(-1, 0, 0, -1, 0, 0));
  228. EXPECT_APPROX_EQ(1, decompR180.scale[0]);
  229. EXPECT_APPROX_EQ(1, decompR180.scale[1]);
  230. EXPECT_APPROX_EQ(1, decompR180.scale[2]);
  231. EXPECT_APPROX_EQ(1, decompR180.quaternion.z());
  232. EXPECT_APPROX_EQ(0, decompR180.quaternion.w());
  233. DecomposedTransform decompR90;
  234. DecomposeTransform(&decompR180, Transform(0, -1, 1, 0, 0, 0));
  235. EXPECT_APPROX_EQ(1, decompR180.scale[0]);
  236. EXPECT_APPROX_EQ(1, decompR180.scale[1]);
  237. EXPECT_APPROX_EQ(1, decompR180.scale[2]);
  238. EXPECT_APPROX_EQ(1 / sqrt(2), decompR180.quaternion.z());
  239. EXPECT_APPROX_EQ(1 / sqrt(2), decompR180.quaternion.w());
  240. DecomposedTransform decompR90Translate;
  241. DecomposeTransform(&decompR90Translate, Transform(0, -1, 1, 0, -1, 1));
  242. EXPECT_APPROX_EQ(1, decompR90Translate.scale[0]);
  243. EXPECT_APPROX_EQ(1, decompR90Translate.scale[1]);
  244. EXPECT_APPROX_EQ(1, decompR90Translate.scale[2]);
  245. EXPECT_APPROX_EQ(-1, decompR90Translate.translate[0]);
  246. EXPECT_APPROX_EQ(1, decompR90Translate.translate[1]);
  247. EXPECT_APPROX_EQ(0, decompR90Translate.translate[2]);
  248. EXPECT_APPROX_EQ(1 / sqrt(2), decompR90Translate.quaternion.z());
  249. EXPECT_APPROX_EQ(1 / sqrt(2), decompR90Translate.quaternion.w());
  250. DecomposedTransform decompSkewRotate;
  251. DecomposeTransform(&decompR90Translate, Transform(1, 1, 1, 0, 0, 0));
  252. EXPECT_APPROX_EQ(sqrt(2), decompR90Translate.scale[0]);
  253. EXPECT_APPROX_EQ(-1 / sqrt(2), decompR90Translate.scale[1]);
  254. EXPECT_APPROX_EQ(1, decompR90Translate.scale[2]);
  255. EXPECT_APPROX_EQ(-1, decompR90Translate.skew[0]);
  256. EXPECT_APPROX_EQ(sin(base::kPiDouble / 8), decompR90Translate.quaternion.z());
  257. EXPECT_APPROX_EQ(cos(base::kPiDouble / 8), decompR90Translate.quaternion.w());
  258. }
  259. TEST(TransformUtilTest, TransformBetweenRects) {
  260. auto verify = [](const RectF& src_rect, const RectF& dst_rect) {
  261. const Transform transform = TransformBetweenRects(src_rect, dst_rect);
  262. // Applies |transform| to calculate the target rectangle from |src_rect|.
  263. // Notes that |transform| is in |src_rect|'s local coordinates.
  264. RectF dst_in_src_coordinates = RectF(src_rect.size());
  265. transform.TransformRect(&dst_in_src_coordinates);
  266. RectF dst_in_parent_coordinates = dst_in_src_coordinates;
  267. dst_in_parent_coordinates.Offset(src_rect.OffsetFromOrigin());
  268. // Verifies that the target rectangle is expected.
  269. EXPECT_EQ(dst_rect, dst_in_parent_coordinates);
  270. };
  271. std::vector<std::pair<const RectF, const RectF>> test_cases{
  272. {RectF(0.f, 0.f, 2.f, 3.f), RectF(3.f, 5.f, 4.f, 9.f)},
  273. {RectF(10.f, 7.f, 2.f, 6.f), RectF(4.f, 2.f, 1.f, 12.f)},
  274. {RectF(0.f, 0.f, 3.f, 5.f), RectF(0.f, 0.f, 6.f, 2.5f)}};
  275. for (const auto& test_case : test_cases) {
  276. verify(test_case.first, test_case.second);
  277. verify(test_case.second, test_case.first);
  278. }
  279. // Tests the case where the destination is an empty rectangle.
  280. verify(RectF(0.f, 0.f, 3.f, 5.f), RectF());
  281. }
  282. TEST(TransformUtilTest, Transform2dScaleComponents) {
  283. // Values to test quiet NaN, infinity, and a denormal float if they're
  284. // present; zero otherwise (since for the case this is used for, it
  285. // should produce the same result).
  286. const float quiet_NaN_or_zero = std::numeric_limits<float>::has_quiet_NaN
  287. ? std::numeric_limits<float>::quiet_NaN()
  288. : 0;
  289. const float infinity_or_zero = std::numeric_limits<float>::has_infinity
  290. ? std::numeric_limits<float>::infinity()
  291. : 0;
  292. const float denorm_min_or_zero =
  293. (std::numeric_limits<float>::has_denorm == std::denorm_present)
  294. ? std::numeric_limits<float>::denorm_min()
  295. : 0;
  296. const struct {
  297. Transform transform;
  298. absl::optional<Vector2dF> expected_scale;
  299. } tests[] = {
  300. // clang-format off
  301. // A matrix with only scale and translation.
  302. {Transform(3, 0, 0, -23,
  303. 0, 7, 0, 31,
  304. 0, 0, 11, 47,
  305. 0, 0, 0, 1),
  306. Vector2dF(3, 7)},
  307. // Matrices like the first, but also with various
  308. // perspective-altering components.
  309. {Transform(3, 0, 0, -23,
  310. 0, 7, 0, 31,
  311. 0, 0, 11, 47,
  312. 0, 0, -0.5, 1),
  313. Vector2dF(3, 7)},
  314. {Transform(3, 0, 0, -23,
  315. 0, 7, 0, 31,
  316. 0, 0, 11, 47,
  317. 0.2f, 0, -0.5f, 1),
  318. absl::nullopt},
  319. {Transform(3, 0, 0, -23,
  320. 0, 7, 0, 31,
  321. 0, 0, 11, 47,
  322. 0.2f, -0.2f, -0.5f, 1),
  323. absl::nullopt},
  324. {Transform(3, 0, 0, -23,
  325. 0, 7, 0, 31,
  326. 0, 0, 11, 47,
  327. 0.2f, -0.2f, -0.5f, 1),
  328. absl::nullopt},
  329. {Transform(3, 0, 0, -23,
  330. 0, 7, 0, 31,
  331. 0, 0, 11, 47,
  332. 0, -0.2f, -0.5f, 1),
  333. absl::nullopt},
  334. {Transform(3, 0, 0, -23,
  335. 0, 7, 0, 31,
  336. 0, 0, 11, 47,
  337. 0, 0, -0.5f, 0.25f),
  338. Vector2dF(12, 28)},
  339. // Matrices like the first, but with some types of rotation.
  340. {Transform(0, 3, 0, -23,
  341. 7, 0, 0, 31,
  342. 0, 0, 11, 47,
  343. 0, 0, 0, 1),
  344. Vector2dF(7, 3)},
  345. {Transform(3, 8, 0, -23,
  346. 4, 6, 0, 31,
  347. 0, 0, 11, 47,
  348. 0, 0, 0, 1),
  349. Vector2dF(5, 10)},
  350. // Combination of rotation and perspective
  351. {Transform(3, 8, 0, -23,
  352. 4, 6, 0, 31,
  353. 0, 0, 11, 47,
  354. 0, 0, 0, 0.25f),
  355. Vector2dF(20, 40)},
  356. // Error handling cases for final perspective component.
  357. {Transform(3, 0, 0, -23,
  358. 0, 7, 0, 31,
  359. 0, 0, 11, 47,
  360. 0, 0, 0, 0),
  361. absl::nullopt},
  362. {Transform(3, 0, 0, -23,
  363. 0, 7, 0, 31,
  364. 0, 0, 11, 47,
  365. 0, 0, 0, quiet_NaN_or_zero),
  366. absl::nullopt},
  367. {Transform(3, 0, 0, -23,
  368. 0, 7, 0, 31,
  369. 0, 0, 11, 47,
  370. 0, 0, 0, infinity_or_zero),
  371. absl::nullopt},
  372. {Transform(3, 0, 0, -23,
  373. 0, 7, 0, 31,
  374. 0, 0, 11, 47,
  375. 0, 0, 0, denorm_min_or_zero),
  376. absl::nullopt},
  377. // clang-format on
  378. };
  379. const float fallback = 1.409718f; // randomly generated in [1,2)
  380. for (const auto& test : tests) {
  381. absl::optional<Vector2dF> try_result =
  382. TryComputeTransform2dScaleComponents(test.transform);
  383. EXPECT_EQ(try_result, test.expected_scale);
  384. Vector2dF result =
  385. ComputeTransform2dScaleComponents(test.transform, fallback);
  386. if (test.expected_scale) {
  387. EXPECT_EQ(result, *test.expected_scale);
  388. } else {
  389. EXPECT_EQ(result, Vector2dF(fallback, fallback));
  390. }
  391. }
  392. }
  393. } // namespace
  394. } // namespace gfx