1 /*
2  * Copyright 2015 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 "SkPathPriv.h"
10 
create_ngon(int n,SkPoint * pts,SkScalar width,SkScalar height)11 static void create_ngon(int n, SkPoint* pts, SkScalar width, SkScalar height) {
12     float angleStep = 360.0f / n, angle = 0.0f, sin, cos;
13     if ((n % 2) == 1) {
14         angle = angleStep/2.0f;
15     }
16 
17     for (int i = 0; i < n; ++i) {
18         sin = SkScalarSinCos(SkDegreesToRadians(angle), &cos);
19         pts[i].fX = -sin * width;
20         pts[i].fY = cos * height;
21         angle += angleStep;
22     }
23 }
24 
25 namespace skiagm {
26 
27 // This GM is intended to exercise Ganesh's handling of convex line-only
28 // paths
29 class ConvexLineOnlyPathsGM : public GM {
30 public:
ConvexLineOnlyPathsGM()31     ConvexLineOnlyPathsGM() {
32         this->setBGColor(0xFFFFFFFF);
33     }
34 
35 protected:
onShortName()36     SkString onShortName() override { return SkString("convex-lineonly-paths"); }
onISize()37     SkISize onISize() override { return SkISize::Make(kGMWidth, kGMHeight); }
runAsBench() const38     bool runAsBench() const override { return true; }
39 
GetPath(int index,int offset,SkPath::Direction dir)40     static SkPath GetPath(int index, int offset, SkPath::Direction dir) {
41         // narrow rect
42         const SkPoint gPoints0[] = {
43             { -1.5f, -50.0f },
44             {  1.5f, -50.0f },
45             {  1.5f,  50.0f },
46             { -1.5f,  50.0f }
47         };
48         // narrow rect on an angle
49         const SkPoint gPoints1[] = {
50             { -50.0f, -49.0f },
51             { -49.0f, -50.0f },
52             {  50.0f,  49.0f },
53             {  49.0f,  50.0f }
54         };
55         // trap - narrow on top - wide on bottom
56         const SkPoint gPoints2[] = {
57             { -10.0f, -50.0f },
58             {  10.0f, -50.0f },
59             {  50.0f,  50.0f },
60             { -50.0f,  50.0f }
61         };
62         // wide skewed rect
63         const SkPoint gPoints3[] = {
64             { -50.0f, -50.0f },
65             {   0.0f, -50.0f },
66             {  50.0f,  50.0f },
67             {   0.0f,  50.0f }
68         };
69         // thin rect with colinear-ish lines
70         const SkPoint gPoints4[] = {
71             { -6.0f, -50.0f },
72             {  4.0f, -50.0f },
73             {  5.0f, -25.0f },
74             {  6.0f,   0.0f },
75             {  5.0f,  25.0f },
76             {  4.0f,  50.0f },
77             { -4.0f,  50.0f }
78         };
79         // degenerate
80         const SkPoint gPoints5[] = {
81             { -0.025f, -0.025f  },
82             {  0.025f, -0.025f  },
83             {  0.025f,  0.025f },
84             { -0.025f,  0.025f }
85         };
86         // Triangle in which the first point should fuse with last
87         const SkPoint gPoints6[] = {
88             { -20.0f, -13.0f },
89             { -20.0f, -13.05f },
90             {  20.0f, -13.0f },
91             {  20.0f,  27.0f }
92         };
93         // thin rect with colinear lines
94         const SkPoint gPoints7[] = {
95             { -10.0f, -50.0f },
96             {  10.0f, -50.0f },
97             {  10.0f, -25.0f },
98             {  10.0f,   0.0f },
99             {  10.0f,  25.0f },
100             {  10.0f,  50.0f },
101             { -10.0f,  50.0f }
102         };
103         // capped teardrop
104         const SkPoint gPoints8[] = {
105             {  50.00f,  50.00f },
106             {   0.00f,  50.00f },
107             { -15.45f,  47.55f },
108             { -29.39f,  40.45f },
109             { -40.45f,  29.39f },
110             { -47.55f,  15.45f },
111             { -50.00f,   0.00f },
112             { -47.55f, -15.45f },
113             { -40.45f, -29.39f },
114             { -29.39f, -40.45f },
115             { -15.45f, -47.55f },
116             {   0.00f, -50.00f },
117             {  50.00f, -50.00f }
118         };
119         // teardrop
120         const SkPoint gPoints9[] = {
121             {   4.39f,  40.45f },
122             {  -9.55f,  47.55f },
123             { -25.00f,  50.00f },
124             { -40.45f,  47.55f },
125             { -54.39f,  40.45f },
126             { -65.45f,  29.39f },
127             { -72.55f,  15.45f },
128             { -75.00f,   0.00f },
129             { -72.55f, -15.45f },
130             { -65.45f, -29.39f },
131             { -54.39f, -40.45f },
132             { -40.45f, -47.55f },
133             { -25.0f,  -50.0f },
134             {  -9.55f, -47.55f },
135             {   4.39f, -40.45f },
136             {  75.00f,   0.00f }
137         };
138         // clipped triangle
139         const SkPoint gPoints10[] = {
140             { -10.0f, -50.0f },
141             {  10.0f, -50.0f },
142             {  50.0f,  31.0f },
143             {  40.0f,  50.0f },
144             { -40.0f,  50.0f },
145             { -50.0f,  31.0f },
146         };
147 
148         const SkPoint* gPoints[] = {
149             gPoints0, gPoints1, gPoints2, gPoints3, gPoints4, gPoints5, gPoints6,
150             gPoints7, gPoints8, gPoints9, gPoints10,
151         };
152 
153         const size_t gSizes[] = {
154             SK_ARRAY_COUNT(gPoints0),
155             SK_ARRAY_COUNT(gPoints1),
156             SK_ARRAY_COUNT(gPoints2),
157             SK_ARRAY_COUNT(gPoints3),
158             SK_ARRAY_COUNT(gPoints4),
159             SK_ARRAY_COUNT(gPoints5),
160             SK_ARRAY_COUNT(gPoints6),
161             SK_ARRAY_COUNT(gPoints7),
162             SK_ARRAY_COUNT(gPoints8),
163             SK_ARRAY_COUNT(gPoints9),
164             SK_ARRAY_COUNT(gPoints10),
165         };
166         static_assert(SK_ARRAY_COUNT(gSizes) == SK_ARRAY_COUNT(gPoints), "array_mismatch");
167 
168         SkAutoTDeleteArray<SkPoint> data(nullptr);
169         const SkPoint* points;
170         int numPts;
171         if (index < (int) SK_ARRAY_COUNT(gPoints)) {
172             // manually specified
173             points = gPoints[index];
174             numPts = (int) gSizes[index];
175         } else {
176             // procedurally generated
177             SkScalar width = kMaxPathHeight/2;
178             SkScalar height = kMaxPathHeight/2;
179             switch (index-SK_ARRAY_COUNT(gPoints)) {
180             case 0:
181                 numPts = 3;
182                 break;
183             case 1:
184                 numPts = 4;
185                 break;
186             case 2:
187                 numPts = 5;
188                 break;
189             case 3:             // squashed pentagon
190                 numPts = 5;
191                 width = kMaxPathHeight/5;
192                 break;
193             case 4:
194                 numPts = 6;
195                 break;
196             case 5:
197                 numPts = 8;
198                 break;
199             case 6:              // squashed octogon
200                 numPts = 8;
201                 width = kMaxPathHeight/5;
202                 break;
203             case 7:
204                 numPts = 20;
205                 break;
206             case 8:
207                 numPts = 100;
208                 break;
209             default:
210                 numPts = 3;
211                 break;
212             }
213 
214             data.reset(new SkPoint[numPts]);
215 
216             create_ngon(numPts, data.get(), width, height);
217             points = data.get();
218         }
219 
220         SkPath path;
221 
222         if (SkPath::kCW_Direction == dir) {
223             path.moveTo(points[0]);
224             for (int i = 1; i < numPts; ++i) {
225                 path.lineTo(points[i]);
226             }
227         } else {
228             path.moveTo(points[numPts-1]);
229             for (int i = numPts-2; i >= 0; --i) {
230                 path.lineTo(points[i]);
231             }
232         }
233 
234         path.close();
235 #ifdef SK_DEBUG
236         // Each path this method returns should be convex, only composed of
237         // lines, wound the right direction, and short enough to fit in one
238         // of the GMs rows.
239         SkASSERT(path.isConvex());
240         SkASSERT(SkPath::kLine_SegmentMask == path.getSegmentMasks());
241         SkPathPriv::FirstDirection actualDir;
242         SkASSERT(SkPathPriv::CheapComputeFirstDirection(path, &actualDir));
243         SkASSERT(SkPathPriv::AsFirstDirection(dir) == actualDir);
244         SkRect bounds = path.getBounds();
245         SkASSERT(SkScalarNearlyEqual(bounds.centerX(), 0.0f));
246         SkASSERT(bounds.height() <= kMaxPathHeight);
247 #endif
248         return path;
249     }
250 
251     // Draw a single path several times, shrinking it, flipping its direction
252     // and changing its start vertex each time.
drawPath(SkCanvas * canvas,int index,SkPoint * offset)253     void drawPath(SkCanvas* canvas, int index, SkPoint* offset) {
254 
255         SkPoint center;
256         {
257             SkPath path = GetPath(index, 0, SkPath::kCW_Direction);
258             if (offset->fX+path.getBounds().width() > kGMWidth) {
259                 offset->fX = 0;
260                 offset->fY += kMaxPathHeight;
261             }
262             center = { offset->fX + SkScalarHalf(path.getBounds().width()), offset->fY};
263             offset->fX += path.getBounds().width();
264         }
265 
266         const SkColor colors[2] = { SK_ColorBLACK, SK_ColorWHITE };
267         const SkPath::Direction dirs[2] = { SkPath::kCW_Direction, SkPath::kCCW_Direction };
268         const float scales[] = { 1.0f, 0.75f, 0.5f, 0.25f, 0.1f, 0.01f, 0.001f };
269 
270         SkPaint paint;
271         paint.setAntiAlias(true);
272 
273         for (size_t i = 0; i < SK_ARRAY_COUNT(scales); ++i) {
274             SkPath path = GetPath(index, (int) i, dirs[i%2]);
275 
276             canvas->save();
277                 canvas->translate(center.fX, center.fY);
278                 canvas->scale(scales[i], scales[i]);
279                 paint.setColor(colors[i%2]);
280                 canvas->drawPath(path, paint);
281             canvas->restore();
282         }
283     }
284 
onDraw(SkCanvas * canvas)285     void onDraw(SkCanvas* canvas) override {
286         // the right edge of the last drawn path
287         SkPoint offset = { 0, SkScalarHalf(kMaxPathHeight) };
288 
289         for (int i = 0; i < kNumPaths; ++i) {
290             this->drawPath(canvas, i, &offset);
291         }
292 
293         // Repro for crbug.com/472723 (Missing AA on portions of graphic with GPU rasterization)
294         {
295             canvas->translate(356.0f, 50.0f);
296 
297             SkPaint p;
298             p.setAntiAlias(true);
299 
300             SkPath p1;
301             p1.moveTo(60.8522949f, 364.671021f);
302             p1.lineTo(59.4380493f, 364.671021f);
303             p1.lineTo(385.414276f, 690.647217f);
304             p1.lineTo(386.121399f, 689.940125f);
305             canvas->drawPath(p1, p);
306         }
307     }
308 
309 private:
310     static const int kNumPaths      = 20;
311     static const int kMaxPathHeight = 100;
312     static const int kGMWidth       = 512;
313     static const int kGMHeight      = 512;
314 
315     typedef GM INHERITED;
316 };
317 
318 //////////////////////////////////////////////////////////////////////////////
319 
320 DEF_GM(return new ConvexLineOnlyPathsGM;)
321 }
322