SkottieTest.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /*
  2. * Copyright 2018 Google Inc.
  3. *
  4. * Use of this source code is governed by a BSD-style license that can be
  5. * found in the LICENSE file.
  6. */
  7. #include "include/core/SkMatrix.h"
  8. #include "include/core/SkStream.h"
  9. #include "include/core/SkTextBlob.h"
  10. #include "include/core/SkTypeface.h"
  11. #include "modules/skottie/include/Skottie.h"
  12. #include "modules/skottie/include/SkottieProperty.h"
  13. #include "modules/skottie/src/text/SkottieShaper.h"
  14. #include "src/core/SkTextBlobPriv.h"
  15. #include "tests/Test.h"
  16. #include <cmath>
  17. #include <tuple>
  18. #include <vector>
  19. using namespace skottie;
  20. DEF_TEST(Skottie_OssFuzz8956, reporter) {
  21. static constexpr char json[] =
  22. "{\"v\":\" \",\"fr\":3,\"w\":4,\"h\":3,\"layers\":[{\"ty\": 1, \"sw\": 10, \"sh\": 10,"
  23. " \"sc\":\"#ffffff\", \"ks\":{\"o\":{\"a\": true, \"k\":"
  24. " [{\"t\": 0, \"s\": 0, \"e\": 1, \"i\": {\"x\":[]}}]}}}]}";
  25. SkMemoryStream stream(json, strlen(json));
  26. // Passes if parsing doesn't crash.
  27. auto animation = Animation::Make(&stream);
  28. }
  29. DEF_TEST(Skottie_Properties, reporter) {
  30. static constexpr char json[] = R"({
  31. "v": "5.2.1",
  32. "w": 100,
  33. "h": 100,
  34. "fr": 1,
  35. "ip": 0,
  36. "op": 1,
  37. "layers": [
  38. {
  39. "ty": 4,
  40. "nm": "layer_0",
  41. "ind": 0,
  42. "ip": 0,
  43. "op": 1,
  44. "ks": {
  45. "o": { "a": 0, "k": 50 }
  46. },
  47. "shapes": [
  48. {
  49. "ty": "el",
  50. "nm": "geometry_0",
  51. "p": { "a": 0, "k": [ 50, 50 ] },
  52. "s": { "a": 0, "k": [ 50, 50 ] }
  53. },
  54. {
  55. "ty": "fl",
  56. "nm": "fill_0",
  57. "c": { "a": 0, "k": [ 1, 0, 0] }
  58. },
  59. {
  60. "ty": "tr",
  61. "nm": "shape_transform_0",
  62. "o": { "a": 0, "k": 100 },
  63. "s": { "a": 0, "k": [ 50, 50 ] }
  64. }
  65. ]
  66. }
  67. ]
  68. })";
  69. class TestPropertyObserver final : public PropertyObserver {
  70. public:
  71. struct ColorInfo {
  72. SkString node_name;
  73. SkColor color;
  74. };
  75. struct OpacityInfo {
  76. SkString node_name;
  77. float opacity;
  78. };
  79. struct TransformInfo {
  80. SkString node_name;
  81. skottie::TransformPropertyValue transform;
  82. };
  83. void onColorProperty(const char node_name[],
  84. const PropertyObserver::LazyHandle<ColorPropertyHandle>& lh) override {
  85. fColors.push_back({SkString(node_name), lh()->get()});
  86. }
  87. void onOpacityProperty(const char node_name[],
  88. const PropertyObserver::LazyHandle<OpacityPropertyHandle>& lh) override {
  89. fOpacities.push_back({SkString(node_name), lh()->get()});
  90. }
  91. void onTransformProperty(const char node_name[],
  92. const PropertyObserver::LazyHandle<TransformPropertyHandle>& lh) override {
  93. fTransforms.push_back({SkString(node_name), lh()->get()});
  94. }
  95. const std::vector<ColorInfo>& colors() const { return fColors; }
  96. const std::vector<OpacityInfo>& opacities() const { return fOpacities; }
  97. const std::vector<TransformInfo>& transforms() const { return fTransforms; }
  98. private:
  99. std::vector<ColorInfo> fColors;
  100. std::vector<OpacityInfo> fOpacities;
  101. std::vector<TransformInfo> fTransforms;
  102. };
  103. SkMemoryStream stream(json, strlen(json));
  104. auto observer = sk_make_sp<TestPropertyObserver>();
  105. auto animation = skottie::Animation::Builder()
  106. .setPropertyObserver(observer)
  107. .make(&stream);
  108. REPORTER_ASSERT(reporter, animation);
  109. const auto& colors = observer->colors();
  110. REPORTER_ASSERT(reporter, colors.size() == 1);
  111. REPORTER_ASSERT(reporter, colors[0].node_name.equals("fill_0"));
  112. REPORTER_ASSERT(reporter, colors[0].color == 0xffff0000);
  113. const auto& opacities = observer->opacities();
  114. REPORTER_ASSERT(reporter, opacities.size() == 2);
  115. REPORTER_ASSERT(reporter, opacities[0].node_name.equals("shape_transform_0"));
  116. REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[0].opacity, 100));
  117. REPORTER_ASSERT(reporter, opacities[1].node_name.equals("layer_0"));
  118. REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[1].opacity, 50));
  119. const auto& transforms = observer->transforms();
  120. REPORTER_ASSERT(reporter, transforms.size() == 2);
  121. REPORTER_ASSERT(reporter, transforms[0].node_name.equals("layer_0"));
  122. REPORTER_ASSERT(reporter, transforms[0].transform == skottie::TransformPropertyValue({
  123. SkPoint::Make(0, 0),
  124. SkPoint::Make(0, 0),
  125. SkVector::Make(100, 100),
  126. 0,
  127. 0,
  128. 0
  129. }));
  130. REPORTER_ASSERT(reporter, transforms[1].node_name.equals("shape_transform_0"));
  131. REPORTER_ASSERT(reporter, transforms[1].transform == skottie::TransformPropertyValue({
  132. SkPoint::Make(0, 0),
  133. SkPoint::Make(0, 0),
  134. SkVector::Make(50, 50),
  135. 0,
  136. 0,
  137. 0
  138. }));
  139. }
  140. DEF_TEST(Skottie_Annotations, reporter) {
  141. static constexpr char json[] = R"({
  142. "v": "5.2.1",
  143. "w": 100,
  144. "h": 100,
  145. "fr": 10,
  146. "ip": 0,
  147. "op": 100,
  148. "layers": [
  149. {
  150. "ty": 1,
  151. "ind": 0,
  152. "ip": 0,
  153. "op": 1,
  154. "ks": {
  155. "o": { "a": 0, "k": 50 }
  156. },
  157. "sw": 100,
  158. "sh": 100,
  159. "sc": "#ffffff"
  160. }
  161. ],
  162. "markers": [
  163. {
  164. "cm": "marker_1",
  165. "dr": 25,
  166. "tm": 25
  167. },
  168. {
  169. "cm": "marker_2",
  170. "dr": 0,
  171. "tm": 75
  172. }
  173. ]
  174. })";
  175. class TestMarkerObserver final : public MarkerObserver {
  176. public:
  177. void onMarker(const char name[], float t0, float t1) override {
  178. fMarkers.push_back(std::make_tuple(name, t0, t1));
  179. }
  180. std::vector<std::tuple<std::string, float, float>> fMarkers;
  181. };
  182. SkMemoryStream stream(json, strlen(json));
  183. auto observer = sk_make_sp<TestMarkerObserver>();
  184. auto animation = skottie::Animation::Builder()
  185. .setMarkerObserver(observer)
  186. .make(&stream);
  187. REPORTER_ASSERT(reporter, animation);
  188. REPORTER_ASSERT(reporter, observer->fMarkers.size() == 2ul);
  189. REPORTER_ASSERT(reporter, std::get<0>(observer->fMarkers[0]) == "marker_1");
  190. REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[0]) == 0.25f);
  191. REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[0]) == 0.50f);
  192. REPORTER_ASSERT(reporter, std::get<0>(observer->fMarkers[1]) == "marker_2");
  193. REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[1]) == 0.75f);
  194. REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[1]) == 0.75f);
  195. }
  196. static SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
  197. auto bounds = SkRect::MakeEmpty();
  198. if (!blob) {
  199. return bounds;
  200. }
  201. SkAutoSTArray<16, SkRect> glyphBounds;
  202. SkTextBlobRunIterator it(blob.get());
  203. for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
  204. glyphBounds.reset(SkToInt(it.glyphCount()));
  205. it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
  206. SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning);
  207. for (uint32_t i = 0; i < it.glyphCount(); ++i) {
  208. bounds.join(glyphBounds[i].makeOffset(it.pos()[i * 2 ],
  209. it.pos()[i * 2 + 1]));
  210. }
  211. }
  212. return bounds;
  213. }
  214. static SkRect ComputeShapeResultBounds(const skottie::Shaper::Result& res) {
  215. auto bounds = SkRect::MakeEmpty();
  216. for (const auto& fragment : res.fFragments) {
  217. bounds.join(ComputeBlobBounds(fragment.fBlob).makeOffset(fragment.fPos.x(),
  218. fragment.fPos.y()));
  219. }
  220. return bounds;
  221. }
  222. DEF_TEST(Skottie_Shaper_HAlign, reporter) {
  223. auto typeface = SkTypeface::MakeDefault();
  224. REPORTER_ASSERT(reporter, typeface);
  225. static constexpr struct {
  226. SkScalar text_size,
  227. tolerance;
  228. } kTestSizes[] = {
  229. // These gross tolerances are required for the test to pass on NativeFonts bots.
  230. // Might be worth investigating why we need so much slack.
  231. { 5, 2.0f },
  232. { 10, 2.0f },
  233. { 15, 2.4f },
  234. { 25, 4.4f },
  235. };
  236. static constexpr struct {
  237. SkTextUtils::Align align;
  238. SkScalar l_selector,
  239. r_selector;
  240. } kTestAligns[] = {
  241. { SkTextUtils:: kLeft_Align, 0.0f, 1.0f },
  242. { SkTextUtils::kCenter_Align, 0.5f, 0.5f },
  243. { SkTextUtils:: kRight_Align, 1.0f, 0.0f },
  244. };
  245. const SkString text("Foo, bar.\rBaz.");
  246. const SkPoint text_point = SkPoint::Make(100, 100);
  247. for (const auto& tsize : kTestSizes) {
  248. for (const auto& talign : kTestAligns) {
  249. const skottie::Shaper::TextDesc desc = {
  250. typeface,
  251. tsize.text_size,
  252. tsize.text_size,
  253. 0,
  254. talign.align,
  255. skottie::Shaper::VAlign::kTopBaseline,
  256. Shaper::Flags::kNone
  257. };
  258. const auto shape_result = skottie::Shaper::Shape(text, desc, text_point);
  259. REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
  260. REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
  261. const auto shape_bounds = ComputeShapeResultBounds(shape_result);
  262. REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
  263. const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
  264. REPORTER_ASSERT(reporter,
  265. std::fabs(shape_bounds.left() - expected_l) < tsize.tolerance,
  266. "%f %f %f %f %d", shape_bounds.left(), expected_l, tsize.tolerance,
  267. tsize.text_size, talign.align);
  268. const auto expected_r = text_point.x() + shape_bounds.width() * talign.r_selector;
  269. REPORTER_ASSERT(reporter,
  270. std::fabs(shape_bounds.right() - expected_r) < tsize.tolerance,
  271. "%f %f %f %f %d", shape_bounds.right(), expected_r, tsize.tolerance,
  272. tsize.text_size, talign.align);
  273. }
  274. }
  275. }
  276. DEF_TEST(Skottie_Shaper_VAlign, reporter) {
  277. auto typeface = SkTypeface::MakeDefault();
  278. REPORTER_ASSERT(reporter, typeface);
  279. static constexpr struct {
  280. SkScalar text_size,
  281. tolerance;
  282. } kTestSizes[] = {
  283. // These gross tolerances are required for the test to pass on NativeFonts bots.
  284. // Might be worth investigating why we need so much slack.
  285. { 5, 2.0f },
  286. { 10, 4.0f },
  287. { 15, 5.5f },
  288. { 25, 8.0f },
  289. };
  290. struct {
  291. skottie::Shaper::VAlign align;
  292. SkScalar topFactor;
  293. } kTestAligns[] = {
  294. { skottie::Shaper::VAlign::kVisualTop , 0.0f },
  295. { skottie::Shaper::VAlign::kVisualCenter, 0.5f },
  296. // TODO: any way to test kTopBaseline?
  297. };
  298. const SkString text("Foo, bar.\rBaz.");
  299. const auto text_box = SkRect::MakeXYWH(100, 100, 1000, 1000); // large-enough to avoid breaks.
  300. for (const auto& tsize : kTestSizes) {
  301. for (const auto& talign : kTestAligns) {
  302. const skottie::Shaper::TextDesc desc = {
  303. typeface,
  304. tsize.text_size,
  305. tsize.text_size,
  306. 0,
  307. SkTextUtils::Align::kCenter_Align,
  308. talign.align,
  309. Shaper::Flags::kNone
  310. };
  311. const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
  312. REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
  313. REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
  314. const auto shape_bounds = ComputeShapeResultBounds(shape_result);
  315. REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
  316. const auto v_diff = text_box.height() - shape_bounds.height();
  317. const auto expected_t = text_box.top() + v_diff * talign.topFactor;
  318. REPORTER_ASSERT(reporter,
  319. std::fabs(shape_bounds.top() - expected_t) < tsize.tolerance,
  320. "%f %f %f %f %d", shape_bounds.top(), expected_t, tsize.tolerance,
  321. tsize.text_size, SkToU32(talign.align));
  322. const auto expected_b = text_box.bottom() - v_diff * (1 - talign.topFactor);
  323. REPORTER_ASSERT(reporter,
  324. std::fabs(shape_bounds.bottom() - expected_b) < tsize.tolerance,
  325. "%f %f %f %f %d", shape_bounds.bottom(), expected_b, tsize.tolerance,
  326. tsize.text_size, SkToU32(talign.align));
  327. }
  328. }
  329. }
  330. DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
  331. skottie::Shaper::TextDesc desc = {
  332. SkTypeface::MakeDefault(),
  333. 18,
  334. 18,
  335. 0,
  336. SkTextUtils::Align::kCenter_Align,
  337. Shaper::VAlign::kTop,
  338. Shaper::Flags::kNone
  339. };
  340. const SkString text("Foo bar baz");
  341. const auto text_box = SkRect::MakeWH(100, 100);
  342. {
  343. const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
  344. // Default/consolidated mode => single blob result.
  345. REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
  346. REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
  347. }
  348. {
  349. desc.fFlags = Shaper::Flags::kFragmentGlyphs;
  350. const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
  351. // Fragmented mode => one blob per glyph.
  352. const size_t expectedSize = text.size();
  353. REPORTER_ASSERT(reporter, shape_result.fFragments.size() == expectedSize);
  354. for (size_t i = 0; i < expectedSize; ++i) {
  355. REPORTER_ASSERT(reporter, shape_result.fFragments[i].fBlob);
  356. }
  357. }
  358. }