Cary Clark | db16001 | 2018-08-31 15:07:51 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2018 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 | #include "Sample.h" |
| 8 | #include "SkAnimTimer.h" |
| 9 | #include "SkCanvas.h" |
| 10 | #include "SkGeometry.h" |
| 11 | #include "SkPath.h" |
| 12 | #include <string> |
| 13 | |
| 14 | // This draws an animation where every cubic has a cusp, to test drawing a circle |
| 15 | // at the cusp point. Create a unit square. A cubic with its control points |
| 16 | // at the four corners crossing over itself has a cusp. |
| 17 | |
| 18 | // Project the unit square through a random affine matrix. |
| 19 | // Chop the cubic in two. One half of the cubic will have a cusp |
| 20 | // (unless it was chopped exactly at the cusp point). |
| 21 | |
| 22 | // Running this looks mostly OK, but will occasionally draw something odd. |
| 23 | // The odd parts don't appear related to the cusp code, but are old stroking |
| 24 | // bugs that have not been fixed, yet. |
| 25 | |
| 26 | SkMSec start = 0; |
| 27 | SkMSec curTime; |
| 28 | bool first = true; |
| 29 | |
| 30 | // Create a path with one or two cubics, where one has a cusp. |
| 31 | static SkPath cusp(const SkPoint P[4], SkPoint PP[7], bool& split, int speed, SkScalar phase) { |
| 32 | SkPath path; |
| 33 | path.moveTo(P[0]); |
| 34 | SkScalar t = (curTime % speed) / SkIntToFloat(speed); |
| 35 | t += phase; |
| 36 | if (t > 1) { |
| 37 | t -= 1; |
| 38 | } |
| 39 | if (0 <= t || t >= 1) { |
| 40 | path.cubicTo(P[1], P[2], P[3]); |
| 41 | split = false; |
| 42 | } else { |
| 43 | SkChopCubicAt(P, PP, t); |
| 44 | path.cubicTo(PP[1], PP[2], PP[3]); |
| 45 | path.cubicTo(PP[4], PP[5], PP[6]); |
| 46 | split = true; |
| 47 | } |
| 48 | return path; |
| 49 | } |
| 50 | |
| 51 | // Scale the animation counter to a value that oscillates from -scale to +scale. |
| 52 | static SkScalar linearToLoop(int speed, SkScalar phase, SkScalar scale) { |
| 53 | SkScalar loop; |
| 54 | SkScalar linear = (curTime % speed) / SkIntToFloat(speed); // 0 to 1 |
| 55 | linear += phase; |
| 56 | if (linear > 1) { |
| 57 | linear -= 1; |
| 58 | } |
| 59 | if (linear < .25) { |
| 60 | loop = linear * 4; // 0 to .25 ==> 0 to 1 |
| 61 | } else if (linear < .75) { // .25 to .75 ==> 1 to -1 |
| 62 | loop = (.5 - linear) * 4; |
| 63 | } else { // .75 to 1 ==> -1 to 0 |
| 64 | loop = (linear - 1) * 4; |
| 65 | } |
| 66 | return loop * scale; |
| 67 | } |
| 68 | |
| 69 | struct data { |
| 70 | SkIPoint pt[4]; |
| 71 | } dat[] = { |
| 72 | // When the animation looks funny, pause, and paste the last part of the stream in stdout here. |
| 73 | // Enable the 1st #if to play the recorded stream backwards. |
| 74 | // Enable the 2nd #if and replace the second 'i = ##' with the value of datCount that shows the bug. |
| 75 | {{{0x43480000,0x43960000},{0x4318b999,0x4321570b},{0x432f999a,0x435a0a3d},{0x43311fff,0x43734cce},}}, |
| 76 | {{{0x43480000,0x43960000},{0x431d1ddf,0x4321ae13},{0x4331ddde,0x435c147c},{0x43334001,0x43719997},}}, |
| 77 | {{{0x43480000,0x43960000},{0x43218224,0x43220520},{0x43342223,0x435e1eba},{0x43356001,0x436fe666},}}, |
| 78 | {{{0x43480000,0x43960000},{0x4325a445,0x43225708},{0x43364444,0x43600a3c},{0x43376001,0x436e4ccc},}}, |
| 79 | {{{0x43480000,0x43960000},{0x432a0889,0x4322ae16},{0x43388889,0x4362147b},{0x43398000,0x436c999b},}}, |
| 80 | {{{0x43480000,0x43960000},{0x432e6ccd,0x43230523},{0x433acccd,0x43641eba},{0x433ba000,0x436ae66a},}}, |
| 81 | {{{0x43480000,0x43960000},{0x43328eef,0x4323570c},{0x433ceeee,0x43660a3c},{0x433da000,0x43694cd0},}}, |
| 82 | {{{0x43480000,0x43960000},{0x4336f333,0x4323ae13},{0x433f3333,0x4368147a},{0x433fc000,0x43679998},}}, |
| 83 | {{{0x43480000,0x43960000},{0x433b5777,0x43240520},{0x43417777,0x436a1eb9},{0x4341e000,0x4365e668},}}, |
| 84 | {{{0x43480000,0x43960000},{0x433f799a,0x4324570c},{0x4343999a,0x436c0a3e},{0x4343e000,0x43644cce},}}, |
| 85 | {{{0x43480000,0x43960000},{0x4343ddde,0x4324ae13},{0x4345dddf,0x436e147c},{0x43460000,0x43629996},}}, |
| 86 | {{{0x43480000,0x43960000},{0x43484222,0x4325051e},{0x43482222,0x43701eb9},{0x43481fff,0x4360e666},}}, |
| 87 | {{{0x43480000,0x43960000},{0x434c6446,0x43255709},{0x434a4444,0x43720a3e},{0x434a2002,0x435f4ccc},}}, |
| 88 | {{{0x43480000,0x43960000},{0x4350c888,0x4325ae16},{0x434c8889,0x4374147c},{0x434c3fff,0x435d999a},}}, |
| 89 | {{{0x43480000,0x43960000},{0x43552cce,0x43260521},{0x434ecccd,0x43761eb8},{0x434e6001,0x435be669},}}, |
| 90 | {{{0x43480000,0x43960000},{0x43594eee,0x4326570c},{0x4350eeef,0x43780a3d},{0x43505fff,0x435a4ccf},}}, |
| 91 | {{{0x43480000,0x43960000},{0x435db334,0x4326ae19},{0x43533333,0x437a147c},{0x43528001,0x4358999e},}}, |
| 92 | {{{0x43480000,0x43960000},{0x4361d555,0x43270002},{0x43555555,0x437bfffe},{0x43547fff,0x43570004},}}, |
| 93 | {{{0x43480000,0x43960000},{0x4366399a,0x4327570c},{0x4357999a,0x437e0a3f},{0x4356a001,0x43554ccd},}}, |
| 94 | {{{0x43480000,0x43960000},{0x436a9ddc,0x4327ae12},{0x4359ddde,0x43800a3e},{0x4358bffe,0x43539996},}}, |
| 95 | {{{0x43480000,0x43960000},{0x436f0222,0x4328051c},{0x435c2222,0x43810f5c},{0x435ae000,0x4351e664},}}, |
| 96 | }; |
| 97 | |
| 98 | size_t datCount = SK_ARRAY_COUNT(dat); |
| 99 | |
| 100 | class CuspView : public Sample { |
| 101 | public: |
| 102 | CuspView() {} |
| 103 | protected: |
| 104 | bool onQuery(Sample::Event* evt) override { |
| 105 | if (Sample::TitleQ(*evt)) { |
| 106 | Sample::TitleR(evt, "Cusp"); |
| 107 | return true; |
| 108 | } |
| 109 | return this->INHERITED::onQuery(evt); |
| 110 | } |
| 111 | |
| 112 | void onDrawContent(SkCanvas* canvas) override { |
| 113 | SkPaint p; |
| 114 | p.setAntiAlias(true); |
| 115 | p.setStyle(SkPaint::kStroke_Style); |
| 116 | p.setStrokeWidth(20); |
| 117 | #if 0 // enable to play through the stream above backwards. |
| 118 | SkPath path; |
| 119 | int i; |
| 120 | #if 0 // disable to draw only one problematic cubic |
| 121 | i = --datCount; |
| 122 | #else |
| 123 | i = 14; // index into dat of problematic cubic |
| 124 | #endif |
| 125 | path.moveTo( SkBits2Float(dat[i].pt[0].fX), SkBits2Float(dat[i].pt[0].fY)); |
| 126 | path.cubicTo(SkBits2Float(dat[i].pt[1].fX), SkBits2Float(dat[i].pt[1].fY), |
| 127 | SkBits2Float(dat[i].pt[2].fX), SkBits2Float(dat[i].pt[2].fY), |
| 128 | SkBits2Float(dat[i].pt[3].fX), SkBits2Float(dat[i].pt[3].fY)); |
| 129 | #else |
| 130 | SkPath path; |
| 131 | SkRect rect; |
| 132 | rect.setWH(100, 100); |
| 133 | SkMatrix matrix; |
| 134 | SkScalar vals[9]; |
| 135 | vals[0] = linearToLoop(3000, 0, 1); |
| 136 | vals[1] = linearToLoop(4000, .25, 1.25); |
| 137 | vals[2] = 200; |
| 138 | vals[3] = linearToLoop(5000, .5, 1.5); |
| 139 | vals[4] = linearToLoop(7000, .75, 1.75); |
| 140 | vals[5] = 300; |
| 141 | vals[6] = 0; |
| 142 | vals[7] = 0; |
| 143 | vals[8] = 1; |
| 144 | matrix.set9(vals); |
| 145 | SkPoint pts[4], pp[7]; |
| 146 | matrix.mapRectToQuad(pts, rect); |
| 147 | std::swap(pts[1], pts[2]); |
| 148 | bool split; |
| 149 | path = cusp(pts, pp, split, 8000, .125); |
| 150 | auto debugOutCubic = [](const SkPoint* pts) { |
| 151 | return false; // comment out to capture stream of cusp'd cubics in stdout |
| 152 | SkDebugf("{{"); |
| 153 | for (int i = 0; i < 4; ++i) { |
| 154 | SkDebugf("{0x%08x,0x%08x},", SkFloat2Bits(pts[i].fX), SkFloat2Bits(pts[i].fY)); |
| 155 | } |
| 156 | SkDebugf("}},\n"); |
| 157 | }; |
| 158 | if (split) { |
| 159 | debugOutCubic(&pp[0]); |
| 160 | debugOutCubic(&pp[4]); |
| 161 | } else { |
| 162 | debugOutCubic(&pts[0]); |
| 163 | } |
| 164 | #endif |
| 165 | canvas->drawPath(path, p); |
| 166 | // draw time to make it easier to guess when the bad cubic was drawn |
| 167 | std::string timeStr = std::to_string((float) (curTime - start) / 1000.f); |
| 168 | canvas->drawString(timeStr.c_str(), 20, 20, SkPaint()); |
| 169 | SkDebugf(""); |
| 170 | } |
| 171 | |
| 172 | bool onAnimate(const SkAnimTimer& timer) override { |
| 173 | curTime = timer.msec(); |
| 174 | if (!start) { |
| 175 | start = curTime; |
| 176 | } |
| 177 | return true; |
| 178 | } |
| 179 | |
| 180 | private: |
| 181 | |
| 182 | typedef Sample INHERITED; |
| 183 | }; |
| 184 | |
| 185 | DEF_SAMPLE( return new CuspView(); ) |