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