SamplePathText.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /*
  2. * Copyright 2017 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/SkCanvas.h"
  8. #include "include/core/SkPaint.h"
  9. #include "include/core/SkPath.h"
  10. #include "include/utils/SkRandom.h"
  11. #include "samplecode/Sample.h"
  12. #include "src/core/SkStrike.h"
  13. #include "src/core/SkStrikeCache.h"
  14. #include "src/core/SkStrikeSpec.h"
  15. #include "src/core/SkTaskGroup.h"
  16. #include "tools/ToolUtils.h"
  17. ////////////////////////////////////////////////////////////////////////////////////////////////////
  18. // Static text from paths.
  19. class PathText : public Sample {
  20. public:
  21. constexpr static int kNumPaths = 1500;
  22. virtual const char* getName() const { return "PathText"; }
  23. PathText() {}
  24. virtual void reset() {
  25. for (Glyph& glyph : fGlyphs) {
  26. glyph.reset(fRand, this->width(), this->height());
  27. }
  28. }
  29. void onOnceBeforeDraw() final {
  30. SkFont defaultFont;
  31. SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(defaultFont);
  32. auto cache = strikeSpec.findOrCreateExclusiveStrike();
  33. SkPath glyphPaths[52];
  34. for (int i = 0; i < 52; ++i) {
  35. // I and l are rects on OS X ...
  36. char c = "aQCDEFGH7JKLMNOPBRZTUVWXYSAbcdefghijk1mnopqrstuvwxyz"[i];
  37. SkPackedGlyphID id(defaultFont.unicharToGlyph(c));
  38. sk_ignore_unused_variable(cache->getScalerContext()->getPath(id, &glyphPaths[i]));
  39. }
  40. for (int i = 0; i < kNumPaths; ++i) {
  41. const SkPath& p = glyphPaths[i % 52];
  42. fGlyphs[i].init(fRand, p);
  43. }
  44. this->INHERITED::onOnceBeforeDraw();
  45. this->reset();
  46. }
  47. void onSizeChange() final { this->INHERITED::onSizeChange(); this->reset(); }
  48. SkString name() override { return SkString(this->getName()); }
  49. bool onChar(SkUnichar unichar) override {
  50. if (unichar == 'X') {
  51. fDoClip = !fDoClip;
  52. return true;
  53. }
  54. return false;
  55. }
  56. void onDrawContent(SkCanvas* canvas) override {
  57. if (fDoClip) {
  58. SkPath deviceSpaceClipPath = fClipPath;
  59. deviceSpaceClipPath.transform(SkMatrix::MakeScale(this->width(), this->height()));
  60. canvas->save();
  61. canvas->clipPath(deviceSpaceClipPath, SkClipOp::kDifference, true);
  62. canvas->clear(SK_ColorBLACK);
  63. canvas->restore();
  64. canvas->clipPath(deviceSpaceClipPath, SkClipOp::kIntersect, true);
  65. }
  66. this->drawGlyphs(canvas);
  67. }
  68. virtual void drawGlyphs(SkCanvas* canvas) {
  69. for (Glyph& glyph : fGlyphs) {
  70. SkAutoCanvasRestore acr(canvas, true);
  71. canvas->translate(glyph.fPosition.x(), glyph.fPosition.y());
  72. canvas->scale(glyph.fZoom, glyph.fZoom);
  73. canvas->rotate(glyph.fSpin);
  74. canvas->translate(-glyph.fMidpt.x(), -glyph.fMidpt.y());
  75. canvas->drawPath(glyph.fPath, glyph.fPaint);
  76. }
  77. }
  78. protected:
  79. struct Glyph {
  80. void init(SkRandom& rand, const SkPath& path);
  81. void reset(SkRandom& rand, int w, int h);
  82. SkPath fPath;
  83. SkPaint fPaint;
  84. SkPoint fPosition;
  85. SkScalar fZoom;
  86. SkScalar fSpin;
  87. SkPoint fMidpt;
  88. };
  89. Glyph fGlyphs[kNumPaths];
  90. SkRandom fRand{25};
  91. SkPath fClipPath = ToolUtils::make_star(SkRect{0, 0, 1, 1}, 11, 3);
  92. bool fDoClip = false;
  93. typedef Sample INHERITED;
  94. };
  95. void PathText::Glyph::init(SkRandom& rand, const SkPath& path) {
  96. fPath = path;
  97. fPaint.setAntiAlias(true);
  98. fPaint.setColor(rand.nextU() | 0x80808080);
  99. }
  100. void PathText::Glyph::reset(SkRandom& rand, int w, int h) {
  101. int screensize = SkTMax(w, h);
  102. const SkRect& bounds = fPath.getBounds();
  103. SkScalar t;
  104. fPosition = {rand.nextF() * w, rand.nextF() * h};
  105. t = pow(rand.nextF(), 100);
  106. fZoom = ((1 - t) * screensize / 50 + t * screensize / 3) /
  107. SkTMax(bounds.width(), bounds.height());
  108. fSpin = rand.nextF() * 360;
  109. fMidpt = {bounds.centerX(), bounds.centerY()};
  110. }
  111. ////////////////////////////////////////////////////////////////////////////////////////////////////
  112. // Text from paths with animated transformation matrices.
  113. class MovingPathText : public PathText {
  114. public:
  115. const char* getName() const override { return "MovingPathText"; }
  116. MovingPathText()
  117. : fFrontMatrices(kNumPaths)
  118. , fBackMatrices(kNumPaths) {
  119. }
  120. ~MovingPathText() override {
  121. fBackgroundAnimationTask.wait();
  122. }
  123. void reset() override {
  124. const SkScalar screensize = static_cast<SkScalar>(SkTMax(this->width(), this->height()));
  125. this->INHERITED::reset();
  126. for (auto& v : fVelocities) {
  127. for (SkScalar* d : {&v.fDx, &v.fDy}) {
  128. SkScalar t = pow(fRand.nextF(), 3);
  129. *d = ((1 - t) / 60 + t / 10) * (fRand.nextBool() ? screensize : -screensize);
  130. }
  131. SkScalar t = pow(fRand.nextF(), 25);
  132. v.fDSpin = ((1 - t) * 360 / 7.5 + t * 360 / 1.5) * (fRand.nextBool() ? 1 : -1);
  133. }
  134. // Get valid front data.
  135. fBackgroundAnimationTask.wait();
  136. this->runAnimationTask(0, 0, this->width(), this->height());
  137. memcpy(fFrontMatrices, fBackMatrices, kNumPaths * sizeof(SkMatrix));
  138. fLastTick = 0;
  139. }
  140. bool onAnimate(double nanos) final {
  141. fBackgroundAnimationTask.wait();
  142. this->swapAnimationBuffers();
  143. const double tsec = 1e-9 * nanos;
  144. const double dt = fLastTick ? (1e-9 * nanos - fLastTick) : 0;
  145. fBackgroundAnimationTask.add(std::bind(&MovingPathText::runAnimationTask, this, tsec,
  146. dt, this->width(), this->height()));
  147. fLastTick = 1e-9 * nanos;
  148. return true;
  149. }
  150. /**
  151. * Called on a background thread. Here we can only modify fBackMatrices.
  152. */
  153. virtual void runAnimationTask(double t, double dt, int w, int h) {
  154. for (int idx = 0; idx < kNumPaths; ++idx) {
  155. Velocity* v = &fVelocities[idx];
  156. Glyph* glyph = &fGlyphs[idx];
  157. SkMatrix* backMatrix = &fBackMatrices[idx];
  158. glyph->fPosition.fX += v->fDx * dt;
  159. if (glyph->fPosition.x() < 0) {
  160. glyph->fPosition.fX -= 2 * glyph->fPosition.x();
  161. v->fDx = -v->fDx;
  162. } else if (glyph->fPosition.x() > w) {
  163. glyph->fPosition.fX -= 2 * (glyph->fPosition.x() - w);
  164. v->fDx = -v->fDx;
  165. }
  166. glyph->fPosition.fY += v->fDy * dt;
  167. if (glyph->fPosition.y() < 0) {
  168. glyph->fPosition.fY -= 2 * glyph->fPosition.y();
  169. v->fDy = -v->fDy;
  170. } else if (glyph->fPosition.y() > h) {
  171. glyph->fPosition.fY -= 2 * (glyph->fPosition.y() - h);
  172. v->fDy = -v->fDy;
  173. }
  174. glyph->fSpin += v->fDSpin * dt;
  175. backMatrix->setTranslate(glyph->fPosition.x(), glyph->fPosition.y());
  176. backMatrix->preScale(glyph->fZoom, glyph->fZoom);
  177. backMatrix->preRotate(glyph->fSpin);
  178. backMatrix->preTranslate(-glyph->fMidpt.x(), -glyph->fMidpt.y());
  179. }
  180. }
  181. virtual void swapAnimationBuffers() {
  182. std::swap(fFrontMatrices, fBackMatrices);
  183. }
  184. void drawGlyphs(SkCanvas* canvas) override {
  185. for (int i = 0; i < kNumPaths; ++i) {
  186. SkAutoCanvasRestore acr(canvas, true);
  187. canvas->concat(fFrontMatrices[i]);
  188. canvas->drawPath(fGlyphs[i].fPath, fGlyphs[i].fPaint);
  189. }
  190. }
  191. protected:
  192. struct Velocity {
  193. SkScalar fDx, fDy;
  194. SkScalar fDSpin;
  195. };
  196. Velocity fVelocities[kNumPaths];
  197. SkAutoTMalloc<SkMatrix> fFrontMatrices;
  198. SkAutoTMalloc<SkMatrix> fBackMatrices;
  199. SkTaskGroup fBackgroundAnimationTask;
  200. double fLastTick;
  201. typedef PathText INHERITED;
  202. };
  203. ////////////////////////////////////////////////////////////////////////////////////////////////////
  204. // Text from paths with animated control points.
  205. class WavyPathText : public MovingPathText {
  206. public:
  207. const char* getName() const override { return "WavyPathText"; }
  208. WavyPathText()
  209. : fFrontPaths(kNumPaths)
  210. , fBackPaths(kNumPaths) {}
  211. ~WavyPathText() override {
  212. fBackgroundAnimationTask.wait();
  213. }
  214. void reset() override {
  215. fWaves.reset(fRand, this->width(), this->height());
  216. this->INHERITED::reset();
  217. std::copy(fBackPaths.get(), fBackPaths.get() + kNumPaths, fFrontPaths.get());
  218. }
  219. /**
  220. * Called on a background thread. Here we can only modify fBackPaths.
  221. */
  222. void runAnimationTask(double t, double dt, int w, int h) override {
  223. const float tsec = static_cast<float>(t);
  224. this->INHERITED::runAnimationTask(t, 0.5 * dt, w, h);
  225. for (int i = 0; i < kNumPaths; ++i) {
  226. const Glyph& glyph = fGlyphs[i];
  227. const SkMatrix& backMatrix = fBackMatrices[i];
  228. const Sk2f matrix[3] = {
  229. Sk2f(backMatrix.getScaleX(), backMatrix.getSkewY()),
  230. Sk2f(backMatrix.getSkewX(), backMatrix.getScaleY()),
  231. Sk2f(backMatrix.getTranslateX(), backMatrix.getTranslateY())
  232. };
  233. SkPath* backpath = &fBackPaths[i];
  234. backpath->reset();
  235. backpath->setFillType(SkPath::kEvenOdd_FillType);
  236. SkPath::RawIter iter(glyph.fPath);
  237. SkPath::Verb verb;
  238. SkPoint pts[4];
  239. while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
  240. switch (verb) {
  241. case SkPath::kMove_Verb: {
  242. SkPoint pt = fWaves.apply(tsec, matrix, pts[0]);
  243. backpath->moveTo(pt.x(), pt.y());
  244. break;
  245. }
  246. case SkPath::kLine_Verb: {
  247. SkPoint endpt = fWaves.apply(tsec, matrix, pts[1]);
  248. backpath->lineTo(endpt.x(), endpt.y());
  249. break;
  250. }
  251. case SkPath::kQuad_Verb: {
  252. SkPoint controlPt = fWaves.apply(tsec, matrix, pts[1]);
  253. SkPoint endpt = fWaves.apply(tsec, matrix, pts[2]);
  254. backpath->quadTo(controlPt.x(), controlPt.y(), endpt.x(), endpt.y());
  255. break;
  256. }
  257. case SkPath::kClose_Verb: {
  258. backpath->close();
  259. break;
  260. }
  261. case SkPath::kCubic_Verb:
  262. case SkPath::kConic_Verb:
  263. case SkPath::kDone_Verb:
  264. SK_ABORT("Unexpected path verb");
  265. break;
  266. }
  267. }
  268. }
  269. }
  270. void swapAnimationBuffers() override {
  271. this->INHERITED::swapAnimationBuffers();
  272. std::swap(fFrontPaths, fBackPaths);
  273. }
  274. void drawGlyphs(SkCanvas* canvas) override {
  275. for (int i = 0; i < kNumPaths; ++i) {
  276. canvas->drawPath(fFrontPaths[i], fGlyphs[i].fPaint);
  277. }
  278. }
  279. private:
  280. /**
  281. * Describes 4 stacked sine waves that can offset a point as a function of wall time.
  282. */
  283. class Waves {
  284. public:
  285. void reset(SkRandom& rand, int w, int h);
  286. SkPoint apply(float tsec, const Sk2f matrix[3], const SkPoint& pt) const;
  287. private:
  288. constexpr static double kAverageAngle = SK_ScalarPI / 8.0;
  289. constexpr static double kMaxOffsetAngle = SK_ScalarPI / 3.0;
  290. float fAmplitudes[4];
  291. float fFrequencies[4];
  292. float fDirsX[4];
  293. float fDirsY[4];
  294. float fSpeeds[4];
  295. float fOffsets[4];
  296. };
  297. SkAutoTArray<SkPath> fFrontPaths;
  298. SkAutoTArray<SkPath> fBackPaths;
  299. Waves fWaves;
  300. typedef MovingPathText INHERITED;
  301. };
  302. void WavyPathText::Waves::reset(SkRandom& rand, int w, int h) {
  303. const double pixelsPerMeter = 0.06 * SkTMax(w, h);
  304. const double medianWavelength = 8 * pixelsPerMeter;
  305. const double medianWaveAmplitude = 0.05 * 4 * pixelsPerMeter;
  306. const double gravity = 9.8 * pixelsPerMeter;
  307. for (int i = 0; i < 4; ++i) {
  308. const double offsetAngle = (rand.nextF() * 2 - 1) * kMaxOffsetAngle;
  309. const double intensity = pow(2, rand.nextF() * 2 - 1);
  310. const double wavelength = intensity * medianWavelength;
  311. fAmplitudes[i] = intensity * medianWaveAmplitude;
  312. fFrequencies[i] = 2 * SK_ScalarPI / wavelength;
  313. fDirsX[i] = cosf(kAverageAngle + offsetAngle);
  314. fDirsY[i] = sinf(kAverageAngle + offsetAngle);
  315. fSpeeds[i] = -sqrt(gravity * 2 * SK_ScalarPI / wavelength);
  316. fOffsets[i] = rand.nextF() * 2 * SK_ScalarPI;
  317. }
  318. }
  319. SkPoint WavyPathText::Waves::apply(float tsec, const Sk2f matrix[3], const SkPoint& pt) const {
  320. constexpr static int kTablePeriod = 1 << 12;
  321. static float sin2table[kTablePeriod + 1];
  322. static SkOnce initTable;
  323. initTable([]() {
  324. for (int i = 0; i <= kTablePeriod; ++i) {
  325. const double sintheta = sin(i * (SK_ScalarPI / kTablePeriod));
  326. sin2table[i] = static_cast<float>(sintheta * sintheta - 0.5);
  327. }
  328. });
  329. const Sk4f amplitudes = Sk4f::Load(fAmplitudes);
  330. const Sk4f frequencies = Sk4f::Load(fFrequencies);
  331. const Sk4f dirsX = Sk4f::Load(fDirsX);
  332. const Sk4f dirsY = Sk4f::Load(fDirsY);
  333. const Sk4f speeds = Sk4f::Load(fSpeeds);
  334. const Sk4f offsets = Sk4f::Load(fOffsets);
  335. float devicePt[2];
  336. (matrix[0] * pt.x() + matrix[1] * pt.y() + matrix[2]).store(devicePt);
  337. const Sk4f t = (frequencies * (dirsX * devicePt[0] + dirsY * devicePt[1]) +
  338. speeds * tsec +
  339. offsets).abs() * (float(kTablePeriod) / float(SK_ScalarPI));
  340. const Sk4i ipart = SkNx_cast<int>(t);
  341. const Sk4f fpart = t - SkNx_cast<float>(ipart);
  342. int32_t indices[4];
  343. (ipart & (kTablePeriod-1)).store(indices);
  344. const Sk4f left(sin2table[indices[0]], sin2table[indices[1]],
  345. sin2table[indices[2]], sin2table[indices[3]]);
  346. const Sk4f right(sin2table[indices[0] + 1], sin2table[indices[1] + 1],
  347. sin2table[indices[2] + 1], sin2table[indices[3] + 1]);
  348. const Sk4f height = amplitudes * (left * (1.f - fpart) + right * fpart);
  349. Sk4f dy = height * dirsY;
  350. Sk4f dx = height * dirsX;
  351. float offsetY[4], offsetX[4];
  352. (dy + SkNx_shuffle<2,3,0,1>(dy)).store(offsetY); // accumulate.
  353. (dx + SkNx_shuffle<2,3,0,1>(dx)).store(offsetX);
  354. return {devicePt[0] + offsetY[0] + offsetY[1], devicePt[1] - offsetX[0] - offsetX[1]};
  355. }
  356. ////////////////////////////////////////////////////////////////////////////////////////////////////
  357. DEF_SAMPLE( return new WavyPathText; )
  358. DEF_SAMPLE( return new MovingPathText; )
  359. DEF_SAMPLE( return new PathText; )