blob: 7eb85f18ef806d98e12ad3e1ec98ae570a04672d [file] [log] [blame]
Cary Clarkdb160012018-08-31 15:07:51 -04001/*
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
26SkMSec start = 0;
27SkMSec curTime;
28bool first = true;
29
30// Create a path with one or two cubics, where one has a cusp.
31static 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.
52static 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
69struct 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
98size_t datCount = SK_ARRAY_COUNT(dat);
99
100class CuspView : public Sample {
101public:
102 CuspView() {}
103protected:
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
180private:
181
182 typedef Sample INHERITED;
183};
184
185DEF_SAMPLE( return new CuspView(); )