|  | /* | 
|  | * Copyright 2018 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include <string> | 
|  | #include "include/core/SkCanvas.h" | 
|  | #include "include/core/SkFont.h" | 
|  | #include "include/core/SkPath.h" | 
|  | #include "samplecode/Sample.h" | 
|  | #include "src/core/SkGeometry.h" | 
|  | #include "tools/timer/TimeUtils.h" | 
|  |  | 
|  | // This draws an animation where every cubic has a cusp, to test drawing a circle | 
|  | // at the cusp point. Create a unit square. A cubic with its control points | 
|  | // at the four corners crossing over itself has a cusp. | 
|  |  | 
|  | // Project the unit square through a random affine matrix. | 
|  | // Chop the cubic in two. One half of the cubic will have a cusp | 
|  | // (unless it was chopped exactly at the cusp point). | 
|  |  | 
|  | // Running this looks mostly OK, but will occasionally draw something odd. | 
|  | // The odd parts don't appear related to the cusp code, but are old stroking | 
|  | // bugs that have not been fixed, yet. | 
|  |  | 
|  | SkMSec start = 0; | 
|  | SkMSec curTime; | 
|  | bool first = true; | 
|  |  | 
|  | // Create a path with one or two cubics, where one has a cusp. | 
|  | static SkPath cusp(const SkPoint P[4], SkPoint PP[7], bool& split, int speed, SkScalar phase) { | 
|  | SkPath path; | 
|  | path.moveTo(P[0]); | 
|  | SkScalar t = (curTime % speed) / SkIntToFloat(speed); | 
|  | t += phase; | 
|  | if (t > 1) { | 
|  | t -= 1; | 
|  | } | 
|  | if (0 <= t || t >= 1) { | 
|  | path.cubicTo(P[1], P[2], P[3]); | 
|  | split = false; | 
|  | } else { | 
|  | SkChopCubicAt(P, PP, t); | 
|  | path.cubicTo(PP[1], PP[2], PP[3]); | 
|  | path.cubicTo(PP[4], PP[5], PP[6]); | 
|  | split = true; | 
|  | } | 
|  | return path; | 
|  | } | 
|  |  | 
|  | // Scale the animation counter to a value that oscillates from -scale to +scale. | 
|  | static SkScalar linearToLoop(int speed, SkScalar phase, SkScalar scale) { | 
|  | SkScalar loop; | 
|  | SkScalar linear = (curTime % speed) / SkIntToFloat(speed);  // 0 to 1 | 
|  | linear += phase; | 
|  | if (linear > 1) { | 
|  | linear -= 1; | 
|  | } | 
|  | if (linear < .25) { | 
|  | loop = linear * 4;     //  0 to .25  ==> 0 to  1 | 
|  | } else if (linear < .75) { // .25 to .75 ==> 1 to -1 | 
|  | loop = (.5 - linear) * 4; | 
|  | } else  {                  // .75 to 1   ==> -1 to 0 | 
|  | loop = (linear - 1) * 4; | 
|  | } | 
|  | return loop * scale; | 
|  | } | 
|  |  | 
|  | struct data { | 
|  | SkIPoint pt[4]; | 
|  | } dat[] = { | 
|  | // When the animation looks funny, pause, and paste the last part of the stream in stdout here. | 
|  | // Enable the 1st #if to play the recorded stream backwards. | 
|  | // Enable the 2nd #if and replace the second 'i = ##' with the value of datCount that shows the bug. | 
|  | {{{0x43480000,0x43960000},{0x4318b999,0x4321570b},{0x432f999a,0x435a0a3d},{0x43311fff,0x43734cce},}}, | 
|  | {{{0x43480000,0x43960000},{0x431d1ddf,0x4321ae13},{0x4331ddde,0x435c147c},{0x43334001,0x43719997},}}, | 
|  | {{{0x43480000,0x43960000},{0x43218224,0x43220520},{0x43342223,0x435e1eba},{0x43356001,0x436fe666},}}, | 
|  | {{{0x43480000,0x43960000},{0x4325a445,0x43225708},{0x43364444,0x43600a3c},{0x43376001,0x436e4ccc},}}, | 
|  | {{{0x43480000,0x43960000},{0x432a0889,0x4322ae16},{0x43388889,0x4362147b},{0x43398000,0x436c999b},}}, | 
|  | {{{0x43480000,0x43960000},{0x432e6ccd,0x43230523},{0x433acccd,0x43641eba},{0x433ba000,0x436ae66a},}}, | 
|  | {{{0x43480000,0x43960000},{0x43328eef,0x4323570c},{0x433ceeee,0x43660a3c},{0x433da000,0x43694cd0},}}, | 
|  | {{{0x43480000,0x43960000},{0x4336f333,0x4323ae13},{0x433f3333,0x4368147a},{0x433fc000,0x43679998},}}, | 
|  | {{{0x43480000,0x43960000},{0x433b5777,0x43240520},{0x43417777,0x436a1eb9},{0x4341e000,0x4365e668},}}, | 
|  | {{{0x43480000,0x43960000},{0x433f799a,0x4324570c},{0x4343999a,0x436c0a3e},{0x4343e000,0x43644cce},}}, | 
|  | {{{0x43480000,0x43960000},{0x4343ddde,0x4324ae13},{0x4345dddf,0x436e147c},{0x43460000,0x43629996},}}, | 
|  | {{{0x43480000,0x43960000},{0x43484222,0x4325051e},{0x43482222,0x43701eb9},{0x43481fff,0x4360e666},}}, | 
|  | {{{0x43480000,0x43960000},{0x434c6446,0x43255709},{0x434a4444,0x43720a3e},{0x434a2002,0x435f4ccc},}}, | 
|  | {{{0x43480000,0x43960000},{0x4350c888,0x4325ae16},{0x434c8889,0x4374147c},{0x434c3fff,0x435d999a},}}, | 
|  | {{{0x43480000,0x43960000},{0x43552cce,0x43260521},{0x434ecccd,0x43761eb8},{0x434e6001,0x435be669},}}, | 
|  | {{{0x43480000,0x43960000},{0x43594eee,0x4326570c},{0x4350eeef,0x43780a3d},{0x43505fff,0x435a4ccf},}}, | 
|  | {{{0x43480000,0x43960000},{0x435db334,0x4326ae19},{0x43533333,0x437a147c},{0x43528001,0x4358999e},}}, | 
|  | {{{0x43480000,0x43960000},{0x4361d555,0x43270002},{0x43555555,0x437bfffe},{0x43547fff,0x43570004},}}, | 
|  | {{{0x43480000,0x43960000},{0x4366399a,0x4327570c},{0x4357999a,0x437e0a3f},{0x4356a001,0x43554ccd},}}, | 
|  | {{{0x43480000,0x43960000},{0x436a9ddc,0x4327ae12},{0x4359ddde,0x43800a3e},{0x4358bffe,0x43539996},}}, | 
|  | {{{0x43480000,0x43960000},{0x436f0222,0x4328051c},{0x435c2222,0x43810f5c},{0x435ae000,0x4351e664},}}, | 
|  | }; | 
|  |  | 
|  | size_t datCount = SK_ARRAY_COUNT(dat); | 
|  |  | 
|  | class CuspView : public Sample { | 
|  | public: | 
|  | CuspView() {} | 
|  | protected: | 
|  | SkString name() override { return SkString("Cusp"); } | 
|  |  | 
|  | void onDrawContent(SkCanvas* canvas) override { | 
|  | SkPaint p; | 
|  | p.setAntiAlias(true); | 
|  | p.setStyle(SkPaint::kStroke_Style); | 
|  | p.setStrokeWidth(20); | 
|  | #if 0   // enable to play through the stream above backwards. | 
|  | SkPath path; | 
|  | int i; | 
|  | #if 0  // disable to draw only one problematic cubic | 
|  | i = --datCount; | 
|  | #else | 
|  | i = 14; // index into dat of  problematic cubic | 
|  | #endif | 
|  | path.moveTo( SkBits2Float(dat[i].pt[0].fX), SkBits2Float(dat[i].pt[0].fY)); | 
|  | path.cubicTo(SkBits2Float(dat[i].pt[1].fX), SkBits2Float(dat[i].pt[1].fY), | 
|  | SkBits2Float(dat[i].pt[2].fX), SkBits2Float(dat[i].pt[2].fY), | 
|  | SkBits2Float(dat[i].pt[3].fX), SkBits2Float(dat[i].pt[3].fY)); | 
|  | #else | 
|  | SkPath path; | 
|  | SkRect rect; | 
|  | rect.setWH(100, 100); | 
|  | SkMatrix matrix; | 
|  | SkScalar vals[9]; | 
|  | vals[0] = linearToLoop(3000, 0, 1); | 
|  | vals[1] = linearToLoop(4000, .25, 1.25); | 
|  | vals[2] = 200; | 
|  | vals[3] = linearToLoop(5000, .5, 1.5); | 
|  | vals[4] = linearToLoop(7000, .75, 1.75); | 
|  | vals[5] = 300; | 
|  | vals[6] = 0; | 
|  | vals[7] = 0; | 
|  | vals[8] = 1; | 
|  | matrix.set9(vals); | 
|  | SkPoint pts[4], pp[7]; | 
|  | matrix.mapRectToQuad(pts, rect); | 
|  | std::swap(pts[1], pts[2]); | 
|  | bool split; | 
|  | path = cusp(pts, pp, split, 8000, .125); | 
|  | auto debugOutCubic = [](const SkPoint* pts) { | 
|  | return false; // comment out to capture stream of cusp'd cubics in stdout | 
|  | SkDebugf("{{"); | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | SkDebugf("{0x%08x,0x%08x},", SkFloat2Bits(pts[i].fX), SkFloat2Bits(pts[i].fY)); | 
|  | } | 
|  | SkDebugf("}},\n"); | 
|  | }; | 
|  | if (split) { | 
|  | debugOutCubic(&pp[0]); | 
|  | debugOutCubic(&pp[4]); | 
|  | } else { | 
|  | debugOutCubic(&pts[0]); | 
|  | } | 
|  | #endif | 
|  | canvas->drawPath(path, p); | 
|  | // draw time to make it easier to guess when the bad cubic was drawn | 
|  | std::string timeStr = std::to_string((float) (curTime - start) / 1000.f); | 
|  | canvas->drawSimpleText(timeStr.c_str(), timeStr.size(), SkTextEncoding::kUTF8, 20, 20, SkFont(), SkPaint()); | 
|  | SkDebugf(""); | 
|  | } | 
|  |  | 
|  | bool onAnimate(double nanos) override { | 
|  | curTime = TimeUtils::NanosToMSec(nanos); | 
|  | if (!start) { | 
|  | start = curTime; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  |  | 
|  | typedef Sample INHERITED; | 
|  | }; | 
|  |  | 
|  | DEF_SAMPLE( return new CuspView(); ) |