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