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