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