• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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.
19 class PathText : public SampleView {
20 public:
21     constexpr static int kNumPaths = 1500;
getName() const22     virtual const char* getName() const { return "PathText"; }
23 
PathText()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 
reset()42     virtual void reset() {
43         for (Glyph& glyph : fGlyphs) {
44             glyph.reset(fRand, this->width(), this->height());
45         }
46     }
47 
onOnceBeforeDraw()48     void onOnceBeforeDraw() final { this->INHERITED::onOnceBeforeDraw(); this->reset(); }
onSizeChange()49     void onSizeChange() final { this->INHERITED::onSizeChange(); this->reset(); }
50 
onQuery(SkEvent * evt)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 
onDrawContent(SkCanvas * canvas)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 
70 protected:
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 
init(SkRandom & rand,const SkPath & path)89 void PathText::Glyph::init(SkRandom& rand, const SkPath& path) {
90     fPath = path;
91     fPaint.setAntiAlias(true);
92     fPaint.setColor(rand.nextU() | 0x80808080);
93 }
94 
reset(SkRandom & rand,int w,int h)95 void 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.
110 class MovingPathText : public PathText {
111 public:
getName() const112     const char* getName() const override { return "MovingPathText"; }
113 
MovingPathText()114     MovingPathText()
115         : fFrontMatrices(kNumPaths)
116         , fBackMatrices(kNumPaths) {
117     }
118 
~MovingPathText()119     ~MovingPathText() override {
120         fBackgroundAnimationTask.wait();
121     }
122 
reset()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 
onAnimate(const SkAnimTimer & timer)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      */
runAnimationTask(double t,double dt,int w,int h)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 
swapAnimationBuffers()192     virtual void swapAnimationBuffers() {
193         std::swap(fFrontMatrices, fBackMatrices);
194     }
195 
onDrawContent(SkCanvas * canvas)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 
204 protected:
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.
222 class WavyPathText : public MovingPathText {
223 public:
getName() const224     const char* getName() const override { return "WavyPathText"; }
225 
WavyPathText()226     WavyPathText()
227         : fFrontPaths(kNumPaths)
228         , fBackPaths(kNumPaths) {}
229 
~WavyPathText()230     ~WavyPathText() override {
231         fBackgroundAnimationTask.wait();
232     }
233 
reset()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      */
runAnimationTask(double t,double dt,int w,int h)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:
290                         SkFAIL("Unexpected path verb");
291                         break;
292                 }
293             }
294         }
295     }
296 
swapAnimationBuffers()297     void swapAnimationBuffers() override {
298         this->INHERITED::swapAnimationBuffers();
299         fFrontPaths.swap(fBackPaths);
300     }
301 
onDrawContent(SkCanvas * canvas)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 
308 private:
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 
reset(SkRandom & rand,int w,int h)336 void 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 
apply(float tsec,const Sk2f matrix[3],const SkPoint & pt) const356 SkPoint WavyPathText::Waves::apply(float tsec, const Sk2f matrix[3], const SkPoint& pt) const {
357     constexpr static int kTableSize = 4096;
358     static float sin2table[kTableSize];
359     static SkOnce initTable;
360     initTable([]() {
361         for (int i = 0; i <= kTableSize; ++i) {
362             const double sintheta = sin(i * (SK_ScalarPI / kTableSize));
363             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 +
379                     offsets).abs() * (float(kTableSize) / float(SK_ScalarPI));
380 
381     const Sk4i ipart = SkNx_cast<int>(t);
382     const Sk4f fpart = t - SkNx_cast<float>(ipart);
383 
384     int32_t indices[4];
385     (ipart & (kTableSize-1)).store(indices);
386 
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 
405 DEF_SAMPLE( return new WavyPathText; )
406 DEF_SAMPLE( return new MovingPathText; )
407 DEF_SAMPLE( return new PathText; )
408