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