SampleCusp.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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 <string>
  8. #include "include/core/SkCanvas.h"
  9. #include "include/core/SkFont.h"
  10. #include "include/core/SkPath.h"
  11. #include "samplecode/Sample.h"
  12. #include "src/core/SkGeometry.h"
  13. #include "tools/timer/TimeUtils.h"
  14. // This draws an animation where every cubic has a cusp, to test drawing a circle
  15. // at the cusp point. Create a unit square. A cubic with its control points
  16. // at the four corners crossing over itself has a cusp.
  17. // Project the unit square through a random affine matrix.
  18. // Chop the cubic in two. One half of the cubic will have a cusp
  19. // (unless it was chopped exactly at the cusp point).
  20. // Running this looks mostly OK, but will occasionally draw something odd.
  21. // The odd parts don't appear related to the cusp code, but are old stroking
  22. // bugs that have not been fixed, yet.
  23. SkMSec start = 0;
  24. SkMSec curTime;
  25. bool first = true;
  26. // Create a path with one or two cubics, where one has a cusp.
  27. static SkPath cusp(const SkPoint P[4], SkPoint PP[7], bool& split, int speed, SkScalar phase) {
  28. SkPath path;
  29. path.moveTo(P[0]);
  30. SkScalar t = (curTime % speed) / SkIntToFloat(speed);
  31. t += phase;
  32. if (t > 1) {
  33. t -= 1;
  34. }
  35. if (0 <= t || t >= 1) {
  36. path.cubicTo(P[1], P[2], P[3]);
  37. split = false;
  38. } else {
  39. SkChopCubicAt(P, PP, t);
  40. path.cubicTo(PP[1], PP[2], PP[3]);
  41. path.cubicTo(PP[4], PP[5], PP[6]);
  42. split = true;
  43. }
  44. return path;
  45. }
  46. // Scale the animation counter to a value that oscillates from -scale to +scale.
  47. static SkScalar linearToLoop(int speed, SkScalar phase, SkScalar scale) {
  48. SkScalar loop;
  49. SkScalar linear = (curTime % speed) / SkIntToFloat(speed); // 0 to 1
  50. linear += phase;
  51. if (linear > 1) {
  52. linear -= 1;
  53. }
  54. if (linear < .25) {
  55. loop = linear * 4; // 0 to .25 ==> 0 to 1
  56. } else if (linear < .75) { // .25 to .75 ==> 1 to -1
  57. loop = (.5 - linear) * 4;
  58. } else { // .75 to 1 ==> -1 to 0
  59. loop = (linear - 1) * 4;
  60. }
  61. return loop * scale;
  62. }
  63. struct data {
  64. SkIPoint pt[4];
  65. } dat[] = {
  66. // When the animation looks funny, pause, and paste the last part of the stream in stdout here.
  67. // Enable the 1st #if to play the recorded stream backwards.
  68. // Enable the 2nd #if and replace the second 'i = ##' with the value of datCount that shows the bug.
  69. {{{0x43480000,0x43960000},{0x4318b999,0x4321570b},{0x432f999a,0x435a0a3d},{0x43311fff,0x43734cce},}},
  70. {{{0x43480000,0x43960000},{0x431d1ddf,0x4321ae13},{0x4331ddde,0x435c147c},{0x43334001,0x43719997},}},
  71. {{{0x43480000,0x43960000},{0x43218224,0x43220520},{0x43342223,0x435e1eba},{0x43356001,0x436fe666},}},
  72. {{{0x43480000,0x43960000},{0x4325a445,0x43225708},{0x43364444,0x43600a3c},{0x43376001,0x436e4ccc},}},
  73. {{{0x43480000,0x43960000},{0x432a0889,0x4322ae16},{0x43388889,0x4362147b},{0x43398000,0x436c999b},}},
  74. {{{0x43480000,0x43960000},{0x432e6ccd,0x43230523},{0x433acccd,0x43641eba},{0x433ba000,0x436ae66a},}},
  75. {{{0x43480000,0x43960000},{0x43328eef,0x4323570c},{0x433ceeee,0x43660a3c},{0x433da000,0x43694cd0},}},
  76. {{{0x43480000,0x43960000},{0x4336f333,0x4323ae13},{0x433f3333,0x4368147a},{0x433fc000,0x43679998},}},
  77. {{{0x43480000,0x43960000},{0x433b5777,0x43240520},{0x43417777,0x436a1eb9},{0x4341e000,0x4365e668},}},
  78. {{{0x43480000,0x43960000},{0x433f799a,0x4324570c},{0x4343999a,0x436c0a3e},{0x4343e000,0x43644cce},}},
  79. {{{0x43480000,0x43960000},{0x4343ddde,0x4324ae13},{0x4345dddf,0x436e147c},{0x43460000,0x43629996},}},
  80. {{{0x43480000,0x43960000},{0x43484222,0x4325051e},{0x43482222,0x43701eb9},{0x43481fff,0x4360e666},}},
  81. {{{0x43480000,0x43960000},{0x434c6446,0x43255709},{0x434a4444,0x43720a3e},{0x434a2002,0x435f4ccc},}},
  82. {{{0x43480000,0x43960000},{0x4350c888,0x4325ae16},{0x434c8889,0x4374147c},{0x434c3fff,0x435d999a},}},
  83. {{{0x43480000,0x43960000},{0x43552cce,0x43260521},{0x434ecccd,0x43761eb8},{0x434e6001,0x435be669},}},
  84. {{{0x43480000,0x43960000},{0x43594eee,0x4326570c},{0x4350eeef,0x43780a3d},{0x43505fff,0x435a4ccf},}},
  85. {{{0x43480000,0x43960000},{0x435db334,0x4326ae19},{0x43533333,0x437a147c},{0x43528001,0x4358999e},}},
  86. {{{0x43480000,0x43960000},{0x4361d555,0x43270002},{0x43555555,0x437bfffe},{0x43547fff,0x43570004},}},
  87. {{{0x43480000,0x43960000},{0x4366399a,0x4327570c},{0x4357999a,0x437e0a3f},{0x4356a001,0x43554ccd},}},
  88. {{{0x43480000,0x43960000},{0x436a9ddc,0x4327ae12},{0x4359ddde,0x43800a3e},{0x4358bffe,0x43539996},}},
  89. {{{0x43480000,0x43960000},{0x436f0222,0x4328051c},{0x435c2222,0x43810f5c},{0x435ae000,0x4351e664},}},
  90. };
  91. size_t datCount = SK_ARRAY_COUNT(dat);
  92. class CuspView : public Sample {
  93. public:
  94. CuspView() {}
  95. protected:
  96. SkString name() override { return SkString("Cusp"); }
  97. void onDrawContent(SkCanvas* canvas) override {
  98. SkPaint p;
  99. p.setAntiAlias(true);
  100. p.setStyle(SkPaint::kStroke_Style);
  101. p.setStrokeWidth(20);
  102. #if 0 // enable to play through the stream above backwards.
  103. SkPath path;
  104. int i;
  105. #if 0 // disable to draw only one problematic cubic
  106. i = --datCount;
  107. #else
  108. i = 14; // index into dat of problematic cubic
  109. #endif
  110. path.moveTo( SkBits2Float(dat[i].pt[0].fX), SkBits2Float(dat[i].pt[0].fY));
  111. path.cubicTo(SkBits2Float(dat[i].pt[1].fX), SkBits2Float(dat[i].pt[1].fY),
  112. SkBits2Float(dat[i].pt[2].fX), SkBits2Float(dat[i].pt[2].fY),
  113. SkBits2Float(dat[i].pt[3].fX), SkBits2Float(dat[i].pt[3].fY));
  114. #else
  115. SkPath path;
  116. SkRect rect;
  117. rect.setWH(100, 100);
  118. SkMatrix matrix;
  119. SkScalar vals[9];
  120. vals[0] = linearToLoop(3000, 0, 1);
  121. vals[1] = linearToLoop(4000, .25, 1.25);
  122. vals[2] = 200;
  123. vals[3] = linearToLoop(5000, .5, 1.5);
  124. vals[4] = linearToLoop(7000, .75, 1.75);
  125. vals[5] = 300;
  126. vals[6] = 0;
  127. vals[7] = 0;
  128. vals[8] = 1;
  129. matrix.set9(vals);
  130. SkPoint pts[4], pp[7];
  131. matrix.mapRectToQuad(pts, rect);
  132. std::swap(pts[1], pts[2]);
  133. bool split;
  134. path = cusp(pts, pp, split, 8000, .125);
  135. auto debugOutCubic = [](const SkPoint* pts) {
  136. return false; // comment out to capture stream of cusp'd cubics in stdout
  137. SkDebugf("{{");
  138. for (int i = 0; i < 4; ++i) {
  139. SkDebugf("{0x%08x,0x%08x},", SkFloat2Bits(pts[i].fX), SkFloat2Bits(pts[i].fY));
  140. }
  141. SkDebugf("}},\n");
  142. };
  143. if (split) {
  144. debugOutCubic(&pp[0]);
  145. debugOutCubic(&pp[4]);
  146. } else {
  147. debugOutCubic(&pts[0]);
  148. }
  149. #endif
  150. canvas->drawPath(path, p);
  151. // draw time to make it easier to guess when the bad cubic was drawn
  152. std::string timeStr = std::to_string((float) (curTime - start) / 1000.f);
  153. canvas->drawSimpleText(timeStr.c_str(), timeStr.size(), SkTextEncoding::kUTF8, 20, 20, SkFont(), SkPaint());
  154. SkDebugf("");
  155. }
  156. bool onAnimate(double nanos) override {
  157. curTime = TimeUtils::NanosToMSec(nanos);
  158. if (!start) {
  159. start = curTime;
  160. }
  161. return true;
  162. }
  163. private:
  164. typedef Sample INHERITED;
  165. };
  166. DEF_SAMPLE( return new CuspView(); )