xref: /aosp_15_r20/external/skia/tools/viewer/PathTextSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 "include/core/SkCanvas.h"
9 #include "include/core/SkFont.h"
10 #include "include/core/SkPaint.h"
11 #include "include/core/SkPath.h"
12 #include "src/base/SkRandom.h"
13 #include "src/base/SkVx.h"
14 #include "src/core/SkPathPriv.h"
15 #include "src/core/SkStrike.h"
16 #include "src/core/SkStrikeCache.h"
17 #include "src/core/SkStrikeSpec.h"
18 #include "src/core/SkTaskGroup.h"
19 #include "tools/ToolUtils.h"
20 #include "tools/fonts/FontToolUtils.h"
21 #include "tools/viewer/Slide.h"
22 
23 ////////////////////////////////////////////////////////////////////////////////////////////////////
24 // Static text from paths.
25 class PathTextSlide : public Slide {
26     constexpr static int kNumPaths = 1500;
27     SkSize fSize;
28 
29 public:
PathTextSlide()30     PathTextSlide() { fName = "PathText"; }
31 
reset()32     virtual void reset() {
33         for (Glyph& glyph : fGlyphs) {
34             glyph.reset(fRand, fSize.width(), fSize.height());
35         }
36         fGlyphAnimator->reset(&fRand, fSize.width(), fSize.height());
37     }
38 
load(SkScalar w,SkScalar h)39     void load(SkScalar w, SkScalar h) final {
40         fSize = {w, h};
41 
42         SkFont defaultFont = ToolUtils::DefaultFont();
43         SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(defaultFont);
44         SkBulkGlyphMetricsAndPaths pathMaker{strikeSpec};
45         SkPath glyphPaths[52];
46         for (int i = 0; i < 52; ++i) {
47             // I and l are rects on OS X ...
48             char c = "aQCDEFGH7JKLMNOPBRZTUVWXYSAbcdefghijk1mnopqrstuvwxyz"[i];
49             SkGlyphID id(defaultFont.unicharToGlyph(c));
50             const SkGlyph* glyph = pathMaker.glyph(id);
51             if (glyph->path()) {
52                 glyphPaths[i] = *glyph->path();
53             }
54         }
55 
56         for (int i = 0; i < kNumPaths; ++i) {
57             const SkPath& p = glyphPaths[i % 52];
58             fGlyphs[i].init(fRand, p);
59         }
60         this->reset();
61     }
62 
resize(SkScalar w,SkScalar h)63     void resize(SkScalar w, SkScalar h) final {
64         fSize = {w, h};
65         this->reset();
66     }
67 
68     bool onChar(SkUnichar) override;
69 
animate(double nanos)70     bool animate(double nanos) final {
71         return fGlyphAnimator->animate(nanos, fSize.width(), fSize.height());
72     }
73 
draw(SkCanvas * canvas)74     void draw(SkCanvas* canvas) override {
75         if (fDoClip) {
76             SkPath deviceSpaceClipPath = fClipPath;
77             deviceSpaceClipPath.transform(SkMatrix::Scale(fSize.width(), fSize.height()));
78             canvas->save();
79             canvas->clipPath(deviceSpaceClipPath, SkClipOp::kDifference, true);
80             canvas->clear(SK_ColorBLACK);
81             canvas->restore();
82             canvas->clipPath(deviceSpaceClipPath, SkClipOp::kIntersect, true);
83         }
84         fGlyphAnimator->draw(canvas);
85     }
86 
87 protected:
88     struct Glyph {
89         void init(SkRandom& rand, const SkPath& path);
90         void reset(SkRandom& rand, int w, int h);
91 
92         SkPath     fPath;
93         SkPaint    fPaint;
94         SkPoint    fPosition;
95         SkScalar   fZoom;
96         SkScalar   fSpin;
97         SkPoint    fMidpt;
98     };
99 
100     class GlyphAnimator {
101     public:
GlyphAnimator(Glyph * glyphs)102         GlyphAnimator(Glyph* glyphs) : fGlyphs(glyphs) {}
reset(SkRandom *,int screenWidth,int screenHeight)103         virtual void reset(SkRandom*, int screenWidth, int screenHeight) {}
animate(double nanos,int screenWidth,int screenHeight)104         virtual bool animate(double nanos, int screenWidth, int screenHeight) { return false; }
draw(SkCanvas * canvas)105         virtual void draw(SkCanvas* canvas) {
106             for (int i = 0; i < kNumPaths; ++i) {
107                 Glyph& glyph = fGlyphs[i];
108                 SkAutoCanvasRestore acr(canvas, true);
109                 canvas->translate(glyph.fPosition.x(), glyph.fPosition.y());
110                 canvas->scale(glyph.fZoom, glyph.fZoom);
111                 canvas->rotate(glyph.fSpin);
112                 canvas->translate(-glyph.fMidpt.x(), -glyph.fMidpt.y());
113                 canvas->drawPath(glyph.fPath, glyph.fPaint);
114             }
115         }
~GlyphAnimator()116         virtual ~GlyphAnimator() {}
117 
118     protected:
119         Glyph* const fGlyphs;
120     };
121 
122     class MovingGlyphAnimator;
123     class WavyGlyphAnimator;
124 
125     Glyph fGlyphs[kNumPaths];
126     SkRandom fRand{25};
127     SkPath fClipPath = ToolUtils::make_star(SkRect{0, 0, 1, 1}, 11, 3);
128     bool fDoClip = false;
129     std::unique_ptr<GlyphAnimator> fGlyphAnimator = std::make_unique<GlyphAnimator>(fGlyphs);
130 };
131 
init(SkRandom & rand,const SkPath & path)132 void PathTextSlide::Glyph::init(SkRandom& rand, const SkPath& path) {
133     fPath = path;
134     fPaint.setAntiAlias(true);
135     fPaint.setColor(rand.nextU() | 0x80808080);
136 }
137 
reset(SkRandom & rand,int w,int h)138 void PathTextSlide::Glyph::reset(SkRandom& rand, int w, int h) {
139     int screensize = std::max(w, h);
140     const SkRect& bounds = fPath.getBounds();
141     SkScalar t;
142 
143     fPosition = {rand.nextF() * w, rand.nextF() * h};
144     t = pow(rand.nextF(), 100);
145     fZoom = ((1 - t) * screensize / 50 + t * screensize / 3) /
146             std::max(bounds.width(), bounds.height());
147     fSpin = rand.nextF() * 360;
148     fMidpt = {bounds.centerX(), bounds.centerY()};
149 }
150 
151 ////////////////////////////////////////////////////////////////////////////////////////////////////
152 // Text from paths with animated transformation matrices.
153 class PathTextSlide::MovingGlyphAnimator : public PathTextSlide::GlyphAnimator {
154 public:
MovingGlyphAnimator(Glyph * glyphs)155     MovingGlyphAnimator(Glyph* glyphs)
156             : GlyphAnimator(glyphs)
157             , fFrontMatrices(new SkMatrix[kNumPaths])
158             , fBackMatrices(new SkMatrix[kNumPaths]) {
159     }
160 
~MovingGlyphAnimator()161     ~MovingGlyphAnimator() override {
162         fBackgroundAnimationTask.wait();
163     }
164 
reset(SkRandom * rand,int screenWidth,int screenHeight)165     void reset(SkRandom* rand, int screenWidth, int screenHeight) override {
166         const SkScalar screensize = static_cast<SkScalar>(std::max(screenWidth, screenHeight));
167 
168         for (auto& v : fVelocities) {
169             for (SkScalar* d : {&v.fDx, &v.fDy}) {
170                 SkScalar t = pow(rand->nextF(), 3);
171                 *d = ((1 - t) / 60 + t / 10) * (rand->nextBool() ? screensize : -screensize);
172             }
173 
174             SkScalar t = pow(rand->nextF(), 25);
175             v.fDSpin = ((1 - t) * 360 / 7.5 + t * 360 / 1.5) * (rand->nextBool() ? 1 : -1);
176         }
177 
178         // Get valid front data.
179         fBackgroundAnimationTask.wait();
180         this->runAnimationTask(0, 0, screenWidth, screenHeight);
181         std::copy_n(fBackMatrices.get(), kNumPaths, fFrontMatrices.get());
182         fLastTick = 0;
183     }
184 
animate(double nanos,int screenWidth,int screenHeight)185     bool animate(double nanos, int screenWidth, int screenHeight) final {
186         fBackgroundAnimationTask.wait();
187         this->swapAnimationBuffers();
188 
189         const double tsec = 1e-9 * nanos;
190         const double dt = fLastTick ? (1e-9 * nanos - fLastTick) : 0;
191         fBackgroundAnimationTask.add(std::bind(&MovingGlyphAnimator::runAnimationTask, this, tsec,
192                                                dt, screenWidth, screenHeight));
193         fLastTick = 1e-9 * nanos;
194         return true;
195     }
196 
197     /**
198      * Called on a background thread. Here we can only modify fBackMatrices.
199      */
runAnimationTask(double t,double dt,int w,int h)200     virtual void runAnimationTask(double t, double dt, int w, int h) {
201         for (int idx = 0; idx < kNumPaths; ++idx) {
202             Velocity* v = &fVelocities[idx];
203             Glyph* glyph = &fGlyphs[idx];
204             SkMatrix* backMatrix = &fBackMatrices[idx];
205 
206             glyph->fPosition.fX += v->fDx * dt;
207             if (glyph->fPosition.x() < 0) {
208                 glyph->fPosition.fX -= 2 * glyph->fPosition.x();
209                 v->fDx = -v->fDx;
210             } else if (glyph->fPosition.x() > w) {
211                 glyph->fPosition.fX -= 2 * (glyph->fPosition.x() - w);
212                 v->fDx = -v->fDx;
213             }
214 
215             glyph->fPosition.fY += v->fDy * dt;
216             if (glyph->fPosition.y() < 0) {
217                 glyph->fPosition.fY -= 2 * glyph->fPosition.y();
218                 v->fDy = -v->fDy;
219             } else if (glyph->fPosition.y() > h) {
220                 glyph->fPosition.fY -= 2 * (glyph->fPosition.y() - h);
221                 v->fDy = -v->fDy;
222             }
223 
224             glyph->fSpin += v->fDSpin * dt;
225 
226             backMatrix->setTranslate(glyph->fPosition.x(), glyph->fPosition.y());
227             backMatrix->preScale(glyph->fZoom, glyph->fZoom);
228             backMatrix->preRotate(glyph->fSpin);
229             backMatrix->preTranslate(-glyph->fMidpt.x(), -glyph->fMidpt.y());
230         }
231     }
232 
swapAnimationBuffers()233     virtual void swapAnimationBuffers() {
234         std::swap(fFrontMatrices, fBackMatrices);
235     }
236 
draw(SkCanvas * canvas)237     void draw(SkCanvas* canvas) override {
238         for (int i = 0; i < kNumPaths; ++i) {
239             SkAutoCanvasRestore acr(canvas, true);
240             canvas->concat(fFrontMatrices[i]);
241             canvas->drawPath(fGlyphs[i].fPath, fGlyphs[i].fPaint);
242         }
243     }
244 
245 protected:
246     struct Velocity {
247         SkScalar fDx, fDy;
248         SkScalar fDSpin;
249     };
250 
251     Velocity fVelocities[kNumPaths];
252     std::unique_ptr<SkMatrix[]> fFrontMatrices;
253     std::unique_ptr<SkMatrix[]> fBackMatrices;
254     SkTaskGroup fBackgroundAnimationTask;
255     double fLastTick;
256 };
257 
258 
259 ////////////////////////////////////////////////////////////////////////////////////////////////////
260 // Text from paths with animated control points.
261 class PathTextSlide::WavyGlyphAnimator : public PathTextSlide::MovingGlyphAnimator {
262 public:
WavyGlyphAnimator(Glyph * glyphs)263     WavyGlyphAnimator(Glyph* glyphs)
264             : MovingGlyphAnimator(glyphs)
265             , fFrontPaths(new SkPath[kNumPaths])
266             , fBackPaths(new SkPath[kNumPaths]) {
267     }
268 
~WavyGlyphAnimator()269     ~WavyGlyphAnimator() override {
270         fBackgroundAnimationTask.wait();
271     }
272 
reset(SkRandom * rand,int screenWidth,int screenHeight)273     void reset(SkRandom* rand, int screenWidth, int screenHeight) override {
274         fWaves.reset(*rand, screenWidth, screenHeight);
275         this->MovingGlyphAnimator::reset(rand, screenWidth, screenHeight);
276         std::copy(fBackPaths.get(), fBackPaths.get() + kNumPaths, fFrontPaths.get());
277     }
278 
279     /**
280      * Called on a background thread. Here we can only modify fBackPaths.
281      */
runAnimationTask(double t,double dt,int width,int height)282     void runAnimationTask(double t, double dt, int width, int height) override {
283         const float tsec = static_cast<float>(t);
284         this->MovingGlyphAnimator::runAnimationTask(t, 0.5 * dt, width, height);
285 
286         for (int i = 0; i < kNumPaths; ++i) {
287             const Glyph& glyph = fGlyphs[i];
288             const SkMatrix& backMatrix = fBackMatrices[i];
289 
290             const skvx::float2 matrix[3] = {
291                 skvx::float2(backMatrix.getScaleX(), backMatrix.getSkewY()),
292                 skvx::float2(backMatrix.getSkewX(), backMatrix.getScaleY()),
293                 skvx::float2(backMatrix.getTranslateX(), backMatrix.getTranslateY())
294             };
295 
296             SkPath* backpath = &fBackPaths[i];
297             backpath->reset();
298             backpath->setFillType(SkPathFillType::kEvenOdd);
299 
300             for (auto [verb, pts, w] : SkPathPriv::Iterate(glyph.fPath)) {
301                 switch (verb) {
302                     case SkPathVerb::kMove: {
303                         SkPoint pt = fWaves.apply(tsec, matrix, pts[0]);
304                         backpath->moveTo(pt.x(), pt.y());
305                         break;
306                     }
307                     case SkPathVerb::kLine: {
308                         SkPoint endpt = fWaves.apply(tsec, matrix, pts[1]);
309                         backpath->lineTo(endpt.x(), endpt.y());
310                         break;
311                     }
312                     case SkPathVerb::kQuad: {
313                         SkPoint controlPt = fWaves.apply(tsec, matrix, pts[1]);
314                         SkPoint endpt = fWaves.apply(tsec, matrix, pts[2]);
315                         backpath->quadTo(controlPt.x(), controlPt.y(), endpt.x(), endpt.y());
316                         break;
317                     }
318                     case SkPathVerb::kClose: {
319                         backpath->close();
320                         break;
321                     }
322                     case SkPathVerb::kCubic:
323                     case SkPathVerb::kConic:
324                         SK_ABORT("Unexpected path verb");
325                         break;
326                 }
327             }
328         }
329     }
330 
swapAnimationBuffers()331     void swapAnimationBuffers() override {
332         this->MovingGlyphAnimator::swapAnimationBuffers();
333         std::swap(fFrontPaths, fBackPaths);
334     }
335 
draw(SkCanvas * canvas)336     void draw(SkCanvas* canvas) override {
337         for (int i = 0; i < kNumPaths; ++i) {
338             canvas->drawPath(fFrontPaths[i], fGlyphs[i].fPaint);
339         }
340     }
341 
342 private:
343     /**
344      * Describes 4 stacked sine waves that can offset a point as a function of wall time.
345      */
346     class Waves {
347     public:
348         void reset(SkRandom& rand, int w, int h);
349         SkPoint apply(float tsec, const skvx::float2 matrix[3], const SkPoint& pt) const;
350 
351     private:
352         constexpr static double kAverageAngle = SK_ScalarPI / 8.0;
353         constexpr static double kMaxOffsetAngle = SK_ScalarPI / 3.0;
354 
355         float fAmplitudes[4];
356         float fFrequencies[4];
357         float fDirsX[4];
358         float fDirsY[4];
359         float fSpeeds[4];
360         float fOffsets[4];
361     };
362 
363     std::unique_ptr<SkPath[]> fFrontPaths;
364     std::unique_ptr<SkPath[]> fBackPaths;
365     Waves fWaves;
366 };
367 
reset(SkRandom & rand,int w,int h)368 void PathTextSlide::WavyGlyphAnimator::Waves::reset(SkRandom& rand, int w, int h) {
369     const double pixelsPerMeter = 0.06 * std::max(w, h);
370     const double medianWavelength = 8 * pixelsPerMeter;
371     const double medianWaveAmplitude = 0.05 * 4 * pixelsPerMeter;
372     const double gravity = 9.8 * pixelsPerMeter;
373 
374     for (int i = 0; i < 4; ++i) {
375         const double offsetAngle = (rand.nextF() * 2 - 1) * kMaxOffsetAngle;
376         const double intensity = pow(2, rand.nextF() * 2 - 1);
377         const double wavelength = intensity * medianWavelength;
378 
379         fAmplitudes[i] = intensity * medianWaveAmplitude;
380         fFrequencies[i] = 2 * SK_ScalarPI / wavelength;
381         fDirsX[i] = cosf(kAverageAngle + offsetAngle);
382         fDirsY[i] = sinf(kAverageAngle + offsetAngle);
383         fSpeeds[i] = -sqrt(gravity * 2 * SK_ScalarPI / wavelength);
384         fOffsets[i] = rand.nextF() * 2 * SK_ScalarPI;
385     }
386 }
387 
apply(float tsec,const skvx::float2 matrix[3],const SkPoint & pt) const388 SkPoint PathTextSlide::WavyGlyphAnimator::Waves::apply(float tsec, const skvx::float2 matrix[3],
389                                                   const SkPoint& pt) const {
390     constexpr static int kTablePeriod = 1 << 12;
391     static float sin2table[kTablePeriod + 1];
392     static SkOnce initTable;
393     initTable([]() {
394         for (int i = 0; i <= kTablePeriod; ++i) {
395             const double sintheta = sin(i * (SK_ScalarPI / kTablePeriod));
396             sin2table[i] = static_cast<float>(sintheta * sintheta - 0.5);
397         }
398     });
399 
400      const auto amplitudes = skvx::float4::Load(fAmplitudes);
401      const auto frequencies = skvx::float4::Load(fFrequencies);
402      const auto dirsX = skvx::float4::Load(fDirsX);
403      const auto dirsY = skvx::float4::Load(fDirsY);
404      const auto speeds = skvx::float4::Load(fSpeeds);
405      const auto offsets = skvx::float4::Load(fOffsets);
406 
407     float devicePt[2];
408     (matrix[0] * pt.x() + matrix[1] * pt.y() + matrix[2]).store(devicePt);
409 
410     const skvx::float4 t = abs(frequencies * (dirsX * devicePt[0] + dirsY * devicePt[1]) +
411                                speeds * tsec + offsets) * (float(kTablePeriod) / SK_ScalarPI);
412 
413     const skvx::int4 ipart = skvx::cast<int32_t>(t);
414     const skvx::float4 fpart = t - skvx::cast<float>(ipart);
415 
416     int32_t indices[4];
417     (ipart & (kTablePeriod-1)).store(indices);
418 
419     const skvx::float4 left(sin2table[indices[0]], sin2table[indices[1]],
420                             sin2table[indices[2]], sin2table[indices[3]]);
421     const skvx::float4 right(sin2table[indices[0] + 1], sin2table[indices[1] + 1],
422                              sin2table[indices[2] + 1], sin2table[indices[3] + 1]);
423     const auto height = amplitudes * (left * (1.f - fpart) + right * fpart);
424 
425     auto dy = height * dirsY;
426     auto dx = height * dirsX;
427 
428     float offsetY[4], offsetX[4];
429     (dy + skvx::shuffle<2,3,0,1>(dy)).store(offsetY); // accumulate.
430     (dx + skvx::shuffle<2,3,0,1>(dx)).store(offsetX);
431 
432     return {devicePt[0] + offsetY[0] + offsetY[1], devicePt[1] - offsetX[0] - offsetX[1]};
433 }
434 
onChar(SkUnichar unichar)435 bool PathTextSlide::onChar(SkUnichar unichar) {
436     switch (unichar) {
437         case 'X':
438             fDoClip = !fDoClip;
439             return true;
440         case 'S':
441             fGlyphAnimator = std::make_unique<GlyphAnimator>(fGlyphs);
442             fGlyphAnimator->reset(&fRand, fSize.width(), fSize.height());
443             return true;
444         case 'M':
445             fGlyphAnimator = std::make_unique<MovingGlyphAnimator>(fGlyphs);
446             fGlyphAnimator->reset(&fRand, fSize.width(), fSize.height());
447             return true;
448         case 'W':
449             fGlyphAnimator = std::make_unique<WavyGlyphAnimator>(fGlyphs);
450             fGlyphAnimator->reset(&fRand, fSize.width(), fSize.height());
451             return true;
452     }
453     return false;
454 }
455 
456 ////////////////////////////////////////////////////////////////////////////////////////////////////
457 
458 DEF_SLIDE( return new PathTextSlide; )
459