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