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