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