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 8 #include "gm.h" 9 #include "SkGeometry.h" 10 #include "SkPaint.h" 11 #include "SkPath.h" 12 #include "SkPoint.h" 13 #include "SkRandom.h" 14 15 #include <math.h> 16 17 namespace skiagm { 18 19 // Slices paths into sliver-size contours shaped like ice cream cones. 20 class MandolineSlicer { 21 public: 22 static constexpr int kDefaultSubdivisions = 10; 23 24 MandolineSlicer(SkPoint anchorPt) { 25 fPath.setFillType(SkPath::kEvenOdd_FillType); 26 fPath.setIsVolatile(true); 27 this->reset(anchorPt); 28 } 29 30 void reset(SkPoint anchorPt) { 31 fPath.reset(); 32 fLastPt = fAnchorPt = anchorPt; 33 } 34 35 void sliceLine(SkPoint pt, int numSubdivisions = kDefaultSubdivisions) { 36 if (numSubdivisions <= 0) { 37 fPath.moveTo(fAnchorPt); 38 fPath.lineTo(fLastPt); 39 fPath.lineTo(pt); 40 fPath.close(); 41 fLastPt = pt; 42 return; 43 } 44 float T = this->chooseChopT(numSubdivisions); 45 if (0 == T) { 46 return; 47 } 48 SkPoint midpt = fLastPt * (1 - T) + pt * T; 49 this->sliceLine(midpt, numSubdivisions - 1); 50 this->sliceLine(pt, numSubdivisions - 1); 51 } 52 53 void sliceQuadratic(SkPoint p1, SkPoint p2, int numSubdivisions = kDefaultSubdivisions) { 54 if (numSubdivisions <= 0) { 55 fPath.moveTo(fAnchorPt); 56 fPath.lineTo(fLastPt); 57 fPath.quadTo(p1, p2); 58 fPath.close(); 59 fLastPt = p2; 60 return; 61 } 62 float T = this->chooseChopT(numSubdivisions); 63 if (0 == T) { 64 return; 65 } 66 SkPoint P[3] = {fLastPt, p1, p2}, PP[5]; 67 SkChopQuadAt(P, PP, T); 68 this->sliceQuadratic(PP[1], PP[2], numSubdivisions - 1); 69 this->sliceQuadratic(PP[3], PP[4], numSubdivisions - 1); 70 } 71 72 void sliceCubic(SkPoint p1, SkPoint p2, SkPoint p3, 73 int numSubdivisions = kDefaultSubdivisions) { 74 if (numSubdivisions <= 0) { 75 fPath.moveTo(fAnchorPt); 76 fPath.lineTo(fLastPt); 77 fPath.cubicTo(p1, p2, p3); 78 fPath.close(); 79 fLastPt = p3; 80 return; 81 } 82 float T = this->chooseChopT(numSubdivisions); 83 if (0 == T) { 84 return; 85 } 86 SkPoint P[4] = {fLastPt, p1, p2, p3}, PP[7]; 87 SkChopCubicAt(P, PP, T); 88 this->sliceCubic(PP[1], PP[2], PP[3], numSubdivisions - 1); 89 this->sliceCubic(PP[4], PP[5], PP[6], numSubdivisions - 1); 90 } 91 92 void sliceConic(SkPoint p1, SkPoint p2, float w, int numSubdivisions = kDefaultSubdivisions) { 93 if (numSubdivisions <= 0) { 94 fPath.moveTo(fAnchorPt); 95 fPath.lineTo(fLastPt); 96 fPath.conicTo(p1, p2, w); 97 fPath.close(); 98 fLastPt = p2; 99 return; 100 } 101 float T = this->chooseChopT(numSubdivisions); 102 if (0 == T) { 103 return; 104 } 105 SkConic conic(fLastPt, p1, p2, w), halves[2]; 106 if (!conic.chopAt(T, halves)) { 107 SK_ABORT("SkConic::chopAt failed"); 108 } 109 this->sliceConic(halves[0].fPts[1], halves[0].fPts[2], halves[0].fW, numSubdivisions - 1); 110 this->sliceConic(halves[1].fPts[1], halves[1].fPts[2], halves[1].fW, numSubdivisions - 1); 111 } 112 113 const SkPath& path() const { return fPath; } 114 115 private: 116 float chooseChopT(int numSubdivisions) { 117 SkASSERT(numSubdivisions > 0); 118 if (numSubdivisions > 1) { 119 return .5f; 120 } 121 float T = (0 == fRand.nextU() % 10) ? 0 : scalbnf(1, -(int)fRand.nextRangeU(10, 149)); 122 SkASSERT(T >= 0 && T < 1); 123 return T; 124 } 125 126 SkRandom fRand; 127 SkPath fPath; 128 SkPoint fAnchorPt; 129 SkPoint fLastPt; 130 }; 131 132 class SliverPathsGM : public GM { 133 public: 134 SliverPathsGM() { 135 this->setBGColor(SK_ColorBLACK); 136 } 137 138 protected: 139 SkString onShortName() override { 140 return SkString("mandoline"); 141 } 142 143 SkISize onISize() override { 144 return SkISize::Make(560, 475); 145 } 146 147 void onDraw(SkCanvas* canvas) override { 148 SkPaint paint; 149 paint.setColor(SK_ColorWHITE); 150 paint.setAntiAlias(true); 151 152 MandolineSlicer mandoline({41, 43}); 153 mandoline.sliceCubic({5, 277}, {381, -74}, {243, 162}); 154 mandoline.sliceLine({41, 43}); 155 canvas->drawPath(mandoline.path(), paint); 156 157 mandoline.reset({357.049988f, 446.049988f}); 158 mandoline.sliceCubic({472.750000f, -71.950012f}, {639.750000f, 531.950012f}, 159 {309.049988f, 347.950012f}); 160 mandoline.sliceLine({309.049988f, 419}); 161 mandoline.sliceLine({357.049988f, 446.049988f}); 162 canvas->drawPath(mandoline.path(), paint); 163 164 canvas->save(); 165 canvas->translate(421, 105); 166 canvas->scale(100, 81); 167 mandoline.reset({-cosf(SkDegreesToRadians(-60)), sinf(SkDegreesToRadians(-60))}); 168 mandoline.sliceConic({-2, 0}, 169 {-cosf(SkDegreesToRadians(60)), sinf(SkDegreesToRadians(60))}, .5f); 170 mandoline.sliceConic({-cosf(SkDegreesToRadians(120))*2, sinf(SkDegreesToRadians(120))*2}, 171 {1, 0}, .5f); 172 mandoline.sliceLine({0, 0}); 173 mandoline.sliceLine({-cosf(SkDegreesToRadians(-60)), sinf(SkDegreesToRadians(-60))}); 174 canvas->drawPath(mandoline.path(), paint); 175 canvas->restore(); 176 177 canvas->save(); 178 canvas->translate(150, 300); 179 canvas->scale(75, 75); 180 mandoline.reset({1, 0}); 181 constexpr int nquads = 5; 182 for (int i = 0; i < nquads; ++i) { 183 float theta1 = 2*SK_ScalarPI/nquads * (i + .5f); 184 float theta2 = 2*SK_ScalarPI/nquads * (i + 1); 185 mandoline.sliceQuadratic({cosf(theta1)*2, sinf(theta1)*2}, 186 {cosf(theta2), sinf(theta2)}); 187 } 188 canvas->drawPath(mandoline.path(), paint); 189 canvas->restore(); 190 } 191 }; 192 193 DEF_GM(return new SliverPathsGM;) 194 195 } 196