blob: e350f84412463cd6a09f1ee57c84ba5a91bb0e81 [file] [log] [blame]
csmartdaltoned4984b2017-02-13 14:57:28 -07001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkCanvas.h"
9#include "include/core/SkPaint.h"
10#include "include/core/SkPath.h"
11#include "include/utils/SkRandom.h"
12#include "samplecode/Sample.h"
13#include "src/core/SkStrike.h"
14#include "src/core/SkStrikeCache.h"
Herb Derbybaf64782019-04-17 18:01:04 -040015#include "src/core/SkStrikeSpec.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050016#include "src/core/SkTaskGroup.h"
17#include "tools/ToolUtils.h"
18#include "tools/timer/AnimTimer.h"
csmartdaltoned4984b2017-02-13 14:57:28 -070019
20////////////////////////////////////////////////////////////////////////////////////////////////////
21// Static text from paths.
Ben Wagnerb2c4ea62018-08-08 11:36:17 -040022class PathText : public Sample {
csmartdaltoned4984b2017-02-13 14:57:28 -070023public:
24 constexpr static int kNumPaths = 1500;
25 virtual const char* getName() const { return "PathText"; }
26
Ben Wagner9d289e22018-09-17 15:25:18 -040027 PathText() {}
28
29 virtual void reset() {
30 for (Glyph& glyph : fGlyphs) {
31 glyph.reset(fRand, this->width(), this->height());
32 }
33 }
34
35 void onOnceBeforeDraw() final {
Mike Reed32c60662018-11-28 10:28:07 -050036 SkFont defaultFont;
Herb Derbybaf64782019-04-17 18:01:04 -040037 SkStrikeSpecStorage strikeSpec = SkStrikeSpecStorage::MakeCanonicalized(defaultFont);
38 auto cache = strikeSpec.findOrCreateExclusiveStrike();
csmartdaltoned4984b2017-02-13 14:57:28 -070039 SkPath glyphPaths[52];
40 for (int i = 0; i < 52; ++i) {
41 // I and l are rects on OS X ...
42 char c = "aQCDEFGH7JKLMNOPBRZTUVWXYSAbcdefghijk1mnopqrstuvwxyz"[i];
Mike Reedfdb876d2019-02-01 09:55:20 -050043 SkPackedGlyphID id(defaultFont.unicharToGlyph(c));
Ben Wagner5ddb3082018-03-29 11:18:06 -040044 sk_ignore_unused_variable(cache->getScalerContext()->getPath(id, &glyphPaths[i]));
csmartdaltoned4984b2017-02-13 14:57:28 -070045 }
46
47 for (int i = 0; i < kNumPaths; ++i) {
48 const SkPath& p = glyphPaths[i % 52];
49 fGlyphs[i].init(fRand, p);
50 }
csmartdaltoned4984b2017-02-13 14:57:28 -070051
Ben Wagner9d289e22018-09-17 15:25:18 -040052 this->INHERITED::onOnceBeforeDraw();
53 this->reset();
csmartdaltoned4984b2017-02-13 14:57:28 -070054 }
csmartdaltoned4984b2017-02-13 14:57:28 -070055 void onSizeChange() final { this->INHERITED::onSizeChange(); this->reset(); }
56
Ben Wagnerb2c4ea62018-08-08 11:36:17 -040057 bool onQuery(Sample::Event* evt) final {
58 if (Sample::TitleQ(*evt)) {
59 Sample::TitleR(evt, this->getName());
csmartdaltoned4984b2017-02-13 14:57:28 -070060 return true;
61 }
Chris Dalton7c02cc72017-11-06 14:10:54 -070062 SkUnichar unichar;
Ben Wagnerb2c4ea62018-08-08 11:36:17 -040063 if (Sample::CharQ(*evt, &unichar)) {
Chris Dalton7c02cc72017-11-06 14:10:54 -070064 if (unichar == 'X') {
65 fDoClip = !fDoClip;
Chris Dalton7c02cc72017-11-06 14:10:54 -070066 return true;
67 }
68 }
csmartdaltoned4984b2017-02-13 14:57:28 -070069 return this->INHERITED::onQuery(evt);
70 }
71
72 void onDrawContent(SkCanvas* canvas) override {
Chris Dalton7c02cc72017-11-06 14:10:54 -070073 if (fDoClip) {
Chris Daltona32a3c32017-12-05 10:05:21 -070074 SkPath deviceSpaceClipPath = fClipPath;
75 deviceSpaceClipPath.transform(SkMatrix::MakeScale(this->width(), this->height()));
Chris Dalton7c02cc72017-11-06 14:10:54 -070076 canvas->save();
Chris Daltona32a3c32017-12-05 10:05:21 -070077 canvas->clipPath(deviceSpaceClipPath, SkClipOp::kDifference, true);
Chris Dalton7c02cc72017-11-06 14:10:54 -070078 canvas->clear(SK_ColorBLACK);
79 canvas->restore();
Chris Daltona32a3c32017-12-05 10:05:21 -070080 canvas->clipPath(deviceSpaceClipPath, SkClipOp::kIntersect, true);
Chris Dalton7c02cc72017-11-06 14:10:54 -070081 }
82 this->drawGlyphs(canvas);
83 }
84
85 virtual void drawGlyphs(SkCanvas* canvas) {
csmartdaltoned4984b2017-02-13 14:57:28 -070086 for (Glyph& glyph : fGlyphs) {
87 SkAutoCanvasRestore acr(canvas, true);
88 canvas->translate(glyph.fPosition.x(), glyph.fPosition.y());
89 canvas->scale(glyph.fZoom, glyph.fZoom);
90 canvas->rotate(glyph.fSpin);
91 canvas->translate(-glyph.fMidpt.x(), -glyph.fMidpt.y());
92 canvas->drawPath(glyph.fPath, glyph.fPaint);
93 }
94 }
95
96protected:
97 struct Glyph {
98 void init(SkRandom& rand, const SkPath& path);
99 void reset(SkRandom& rand, int w, int h);
100
101 SkPath fPath;
102 SkPaint fPaint;
103 SkPoint fPosition;
104 SkScalar fZoom;
105 SkScalar fSpin;
106 SkPoint fMidpt;
107 };
108
109 Glyph fGlyphs[kNumPaths];
Chris Dalton7c02cc72017-11-06 14:10:54 -0700110 SkRandom fRand{25};
Mike Kleinea3f0142019-03-20 11:12:10 -0500111 SkPath fClipPath = ToolUtils::make_star(SkRect{0, 0, 1, 1}, 11, 3);
Chris Dalton7c02cc72017-11-06 14:10:54 -0700112 bool fDoClip = false;
csmartdaltoned4984b2017-02-13 14:57:28 -0700113
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400114 typedef Sample INHERITED;
csmartdaltoned4984b2017-02-13 14:57:28 -0700115};
116
117void PathText::Glyph::init(SkRandom& rand, const SkPath& path) {
118 fPath = path;
119 fPaint.setAntiAlias(true);
120 fPaint.setColor(rand.nextU() | 0x80808080);
121}
122
123void PathText::Glyph::reset(SkRandom& rand, int w, int h) {
124 int screensize = SkTMax(w, h);
125 const SkRect& bounds = fPath.getBounds();
126 SkScalar t;
127
128 fPosition = {rand.nextF() * w, rand.nextF() * h};
129 t = pow(rand.nextF(), 100);
130 fZoom = ((1 - t) * screensize / 50 + t * screensize / 3) /
131 SkTMax(bounds.width(), bounds.height());
132 fSpin = rand.nextF() * 360;
133 fMidpt = {bounds.centerX(), bounds.centerY()};
134}
135
136////////////////////////////////////////////////////////////////////////////////////////////////////
137// Text from paths with animated transformation matrices.
138class MovingPathText : public PathText {
139public:
140 const char* getName() const override { return "MovingPathText"; }
141
142 MovingPathText()
143 : fFrontMatrices(kNumPaths)
144 , fBackMatrices(kNumPaths) {
145 }
146
Brian Salomond3b65972017-03-22 12:05:03 -0400147 ~MovingPathText() override {
csmartdaltoned4984b2017-02-13 14:57:28 -0700148 fBackgroundAnimationTask.wait();
149 }
150
151 void reset() override {
152 const SkScalar screensize = static_cast<SkScalar>(SkTMax(this->width(), this->height()));
153 this->INHERITED::reset();
154
155 for (auto& v : fVelocities) {
156 for (SkScalar* d : {&v.fDx, &v.fDy}) {
157 SkScalar t = pow(fRand.nextF(), 3);
158 *d = ((1 - t) / 60 + t / 10) * (fRand.nextBool() ? screensize : -screensize);
159 }
160
161 SkScalar t = pow(fRand.nextF(), 25);
162 v.fDSpin = ((1 - t) * 360 / 7.5 + t * 360 / 1.5) * (fRand.nextBool() ? 1 : -1);
163 }
164
165 // Get valid front data.
166 fBackgroundAnimationTask.wait();
167 this->runAnimationTask(0, 0, this->width(), this->height());
168 memcpy(fFrontMatrices, fBackMatrices, kNumPaths * sizeof(SkMatrix));
169 fLastTick = 0;
170 }
171
Mike Kleincd5104e2019-03-20 11:55:08 -0500172 bool onAnimate(const AnimTimer& timer) final {
csmartdaltoned4984b2017-02-13 14:57:28 -0700173 fBackgroundAnimationTask.wait();
174 this->swapAnimationBuffers();
175
176 const double tsec = timer.secs();
177 const double dt = fLastTick ? (timer.secs() - fLastTick) : 0;
178 fBackgroundAnimationTask.add(std::bind(&MovingPathText::runAnimationTask, this, tsec,
179 dt, this->width(), this->height()));
180 fLastTick = timer.secs();
181 return true;
182 }
183
184 /**
185 * Called on a background thread. Here we can only modify fBackMatrices.
186 */
187 virtual void runAnimationTask(double t, double dt, int w, int h) {
188 for (int idx = 0; idx < kNumPaths; ++idx) {
189 Velocity* v = &fVelocities[idx];
190 Glyph* glyph = &fGlyphs[idx];
191 SkMatrix* backMatrix = &fBackMatrices[idx];
192
193 glyph->fPosition.fX += v->fDx * dt;
194 if (glyph->fPosition.x() < 0) {
195 glyph->fPosition.fX -= 2 * glyph->fPosition.x();
196 v->fDx = -v->fDx;
197 } else if (glyph->fPosition.x() > w) {
198 glyph->fPosition.fX -= 2 * (glyph->fPosition.x() - w);
199 v->fDx = -v->fDx;
200 }
201
202 glyph->fPosition.fY += v->fDy * dt;
203 if (glyph->fPosition.y() < 0) {
204 glyph->fPosition.fY -= 2 * glyph->fPosition.y();
205 v->fDy = -v->fDy;
206 } else if (glyph->fPosition.y() > h) {
207 glyph->fPosition.fY -= 2 * (glyph->fPosition.y() - h);
208 v->fDy = -v->fDy;
209 }
210
211 glyph->fSpin += v->fDSpin * dt;
212
213 backMatrix->setTranslate(glyph->fPosition.x(), glyph->fPosition.y());
214 backMatrix->preScale(glyph->fZoom, glyph->fZoom);
215 backMatrix->preRotate(glyph->fSpin);
216 backMatrix->preTranslate(-glyph->fMidpt.x(), -glyph->fMidpt.y());
217 }
218 }
219
220 virtual void swapAnimationBuffers() {
221 std::swap(fFrontMatrices, fBackMatrices);
222 }
223
Chris Dalton7c02cc72017-11-06 14:10:54 -0700224 void drawGlyphs(SkCanvas* canvas) override {
csmartdaltoned4984b2017-02-13 14:57:28 -0700225 for (int i = 0; i < kNumPaths; ++i) {
226 SkAutoCanvasRestore acr(canvas, true);
227 canvas->concat(fFrontMatrices[i]);
228 canvas->drawPath(fGlyphs[i].fPath, fGlyphs[i].fPaint);
229 }
230 }
231
232protected:
233 struct Velocity {
234 SkScalar fDx, fDy;
235 SkScalar fDSpin;
236 };
237
238 Velocity fVelocities[kNumPaths];
239 SkAutoTMalloc<SkMatrix> fFrontMatrices;
240 SkAutoTMalloc<SkMatrix> fBackMatrices;
241 SkTaskGroup fBackgroundAnimationTask;
242 double fLastTick;
243
244 typedef PathText INHERITED;
245};
246
247
248////////////////////////////////////////////////////////////////////////////////////////////////////
249// Text from paths with animated control points.
250class WavyPathText : public MovingPathText {
251public:
252 const char* getName() const override { return "WavyPathText"; }
253
254 WavyPathText()
255 : fFrontPaths(kNumPaths)
256 , fBackPaths(kNumPaths) {}
257
Brian Salomond3b65972017-03-22 12:05:03 -0400258 ~WavyPathText() override {
csmartdaltoned4984b2017-02-13 14:57:28 -0700259 fBackgroundAnimationTask.wait();
260 }
261
262 void reset() override {
263 fWaves.reset(fRand, this->width(), this->height());
264 this->INHERITED::reset();
265 std::copy(fBackPaths.get(), fBackPaths.get() + kNumPaths, fFrontPaths.get());
266 }
267
268 /**
269 * Called on a background thread. Here we can only modify fBackPaths.
270 */
271 void runAnimationTask(double t, double dt, int w, int h) override {
272 const float tsec = static_cast<float>(t);
273 this->INHERITED::runAnimationTask(t, 0.5 * dt, w, h);
274
275 for (int i = 0; i < kNumPaths; ++i) {
276 const Glyph& glyph = fGlyphs[i];
277 const SkMatrix& backMatrix = fBackMatrices[i];
278
279 const Sk2f matrix[3] = {
280 Sk2f(backMatrix.getScaleX(), backMatrix.getSkewY()),
281 Sk2f(backMatrix.getSkewX(), backMatrix.getScaleY()),
282 Sk2f(backMatrix.getTranslateX(), backMatrix.getTranslateY())
283 };
284
285 SkPath* backpath = &fBackPaths[i];
286 backpath->reset();
287 backpath->setFillType(SkPath::kEvenOdd_FillType);
288
289 SkPath::RawIter iter(glyph.fPath);
290 SkPath::Verb verb;
291 SkPoint pts[4];
292
293 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
294 switch (verb) {
295 case SkPath::kMove_Verb: {
296 SkPoint pt = fWaves.apply(tsec, matrix, pts[0]);
297 backpath->moveTo(pt.x(), pt.y());
298 break;
299 }
300 case SkPath::kLine_Verb: {
301 SkPoint endpt = fWaves.apply(tsec, matrix, pts[1]);
302 backpath->lineTo(endpt.x(), endpt.y());
303 break;
304 }
305 case SkPath::kQuad_Verb: {
306 SkPoint controlPt = fWaves.apply(tsec, matrix, pts[1]);
307 SkPoint endpt = fWaves.apply(tsec, matrix, pts[2]);
308 backpath->quadTo(controlPt.x(), controlPt.y(), endpt.x(), endpt.y());
309 break;
310 }
311 case SkPath::kClose_Verb: {
312 backpath->close();
313 break;
314 }
315 case SkPath::kCubic_Verb:
316 case SkPath::kConic_Verb:
317 case SkPath::kDone_Verb:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -0400318 SK_ABORT("Unexpected path verb");
csmartdaltoned4984b2017-02-13 14:57:28 -0700319 break;
320 }
321 }
322 }
323 }
324
325 void swapAnimationBuffers() override {
326 this->INHERITED::swapAnimationBuffers();
Hal Canary67362362018-04-24 13:58:37 -0400327 std::swap(fFrontPaths, fBackPaths);
csmartdaltoned4984b2017-02-13 14:57:28 -0700328 }
329
Chris Dalton7c02cc72017-11-06 14:10:54 -0700330 void drawGlyphs(SkCanvas* canvas) override {
csmartdaltoned4984b2017-02-13 14:57:28 -0700331 for (int i = 0; i < kNumPaths; ++i) {
332 canvas->drawPath(fFrontPaths[i], fGlyphs[i].fPaint);
333 }
334 }
335
336private:
337 /**
338 * Describes 4 stacked sine waves that can offset a point as a function of wall time.
339 */
340 class Waves {
341 public:
342 void reset(SkRandom& rand, int w, int h);
343 SkPoint apply(float tsec, const Sk2f matrix[3], const SkPoint& pt) const;
344
345 private:
346 constexpr static double kAverageAngle = SK_ScalarPI / 8.0;
347 constexpr static double kMaxOffsetAngle = SK_ScalarPI / 3.0;
348
349 float fAmplitudes[4];
350 float fFrequencies[4];
351 float fDirsX[4];
352 float fDirsY[4];
353 float fSpeeds[4];
354 float fOffsets[4];
355 };
356
357 SkAutoTArray<SkPath> fFrontPaths;
358 SkAutoTArray<SkPath> fBackPaths;
359 Waves fWaves;
360
361 typedef MovingPathText INHERITED;
362};
363
364void WavyPathText::Waves::reset(SkRandom& rand, int w, int h) {
365 const double pixelsPerMeter = 0.06 * SkTMax(w, h);
366 const double medianWavelength = 8 * pixelsPerMeter;
367 const double medianWaveAmplitude = 0.05 * 4 * pixelsPerMeter;
368 const double gravity = 9.8 * pixelsPerMeter;
369
370 for (int i = 0; i < 4; ++i) {
371 const double offsetAngle = (rand.nextF() * 2 - 1) * kMaxOffsetAngle;
372 const double intensity = pow(2, rand.nextF() * 2 - 1);
373 const double wavelength = intensity * medianWavelength;
374
375 fAmplitudes[i] = intensity * medianWaveAmplitude;
376 fFrequencies[i] = 2 * SK_ScalarPI / wavelength;
377 fDirsX[i] = cosf(kAverageAngle + offsetAngle);
378 fDirsY[i] = sinf(kAverageAngle + offsetAngle);
379 fSpeeds[i] = -sqrt(gravity * 2 * SK_ScalarPI / wavelength);
380 fOffsets[i] = rand.nextF() * 2 * SK_ScalarPI;
381 }
382}
383
384SkPoint WavyPathText::Waves::apply(float tsec, const Sk2f matrix[3], const SkPoint& pt) const {
Chris Dalton95cf1662017-06-20 16:32:59 -0700385 constexpr static int kTablePeriod = 1 << 12;
386 static float sin2table[kTablePeriod + 1];
csmartdaltoned4984b2017-02-13 14:57:28 -0700387 static SkOnce initTable;
388 initTable([]() {
Chris Dalton95cf1662017-06-20 16:32:59 -0700389 for (int i = 0; i <= kTablePeriod; ++i) {
390 const double sintheta = sin(i * (SK_ScalarPI / kTablePeriod));
csmartdaltoned4984b2017-02-13 14:57:28 -0700391 sin2table[i] = static_cast<float>(sintheta * sintheta - 0.5);
392 }
393 });
394
395 const Sk4f amplitudes = Sk4f::Load(fAmplitudes);
396 const Sk4f frequencies = Sk4f::Load(fFrequencies);
397 const Sk4f dirsX = Sk4f::Load(fDirsX);
398 const Sk4f dirsY = Sk4f::Load(fDirsY);
399 const Sk4f speeds = Sk4f::Load(fSpeeds);
400 const Sk4f offsets = Sk4f::Load(fOffsets);
401
402 float devicePt[2];
403 (matrix[0] * pt.x() + matrix[1] * pt.y() + matrix[2]).store(devicePt);
404
405 const Sk4f t = (frequencies * (dirsX * devicePt[0] + dirsY * devicePt[1]) +
406 speeds * tsec +
Chris Dalton95cf1662017-06-20 16:32:59 -0700407 offsets).abs() * (float(kTablePeriod) / float(SK_ScalarPI));
csmartdaltoned4984b2017-02-13 14:57:28 -0700408
409 const Sk4i ipart = SkNx_cast<int>(t);
410 const Sk4f fpart = t - SkNx_cast<float>(ipart);
411
412 int32_t indices[4];
Chris Dalton95cf1662017-06-20 16:32:59 -0700413 (ipart & (kTablePeriod-1)).store(indices);
csmartdaltoned4984b2017-02-13 14:57:28 -0700414
415 const Sk4f left(sin2table[indices[0]], sin2table[indices[1]],
416 sin2table[indices[2]], sin2table[indices[3]]);
417 const Sk4f right(sin2table[indices[0] + 1], sin2table[indices[1] + 1],
418 sin2table[indices[2] + 1], sin2table[indices[3] + 1]);
419 const Sk4f height = amplitudes * (left * (1.f - fpart) + right * fpart);
420
421 Sk4f dy = height * dirsY;
422 Sk4f dx = height * dirsX;
423
424 float offsetY[4], offsetX[4];
425 (dy + SkNx_shuffle<2,3,0,1>(dy)).store(offsetY); // accumulate.
Brian Salomon23356442018-11-30 15:33:19 -0500426 (dx + SkNx_shuffle<2,3,0,1>(dx)).store(offsetX);
csmartdaltoned4984b2017-02-13 14:57:28 -0700427
428 return {devicePt[0] + offsetY[0] + offsetY[1], devicePt[1] - offsetX[0] - offsetX[1]};
429}
430
431////////////////////////////////////////////////////////////////////////////////////////////////////
432
433DEF_SAMPLE( return new WavyPathText; )
434DEF_SAMPLE( return new MovingPathText; )
435DEF_SAMPLE( return new PathText; )