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