• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkMatrix.h"
12 #include "include/core/SkPaint.h"
13 #include "include/core/SkPath.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkScalar.h"
17 #include "include/core/SkSize.h"
18 #include "include/core/SkString.h"
19 #include "include/core/SkTypes.h"
20 #include "src/core/SkPathPriv.h"
21 
22 #include <memory>
23 
create_ngon(int n,SkPoint * pts,SkScalar width,SkScalar height)24 static void create_ngon(int n, SkPoint* pts, SkScalar width, SkScalar height) {
25     float angleStep = 360.0f / n, angle = 0.0f;
26     if ((n % 2) == 1) {
27         angle = angleStep/2.0f;
28     }
29 
30     for (int i = 0; i < n; ++i) {
31         pts[i].fX = -SkScalarSin(SkDegreesToRadians(angle)) * width;
32         pts[i].fY =  SkScalarCos(SkDegreesToRadians(angle)) * height;
33         angle += angleStep;
34     }
35 }
36 
37 namespace ConvexLineOnlyData {
38 // narrow rect
39 const SkPoint gPoints0[] = {
40     { -1.5f, -50.0f },
41     { 1.5f, -50.0f },
42     { 1.5f,  50.0f },
43     { -1.5f,  50.0f }
44 };
45 // narrow rect on an angle
46 const SkPoint gPoints1[] = {
47     { -50.0f, -49.0f },
48     { -49.0f, -50.0f },
49     { 50.0f,  49.0f },
50     { 49.0f,  50.0f }
51 };
52 // trap - narrow on top - wide on bottom
53 const SkPoint gPoints2[] = {
54     { -10.0f, -50.0f },
55     { 10.0f, -50.0f },
56     { 50.0f,  50.0f },
57     { -50.0f,  50.0f }
58 };
59 // wide skewed rect
60 const SkPoint gPoints3[] = {
61     { -50.0f, -50.0f },
62     { 0.0f, -50.0f },
63     { 50.0f,  50.0f },
64     { 0.0f,  50.0f }
65 };
66 // thin rect with colinear-ish lines
67 const SkPoint gPoints4[] = {
68     { -6.0f, -50.0f },
69     { 4.0f, -50.0f },
70     { 5.0f, -25.0f },  // remove if collinear diagonal points are not concave
71     { 6.0f,   0.0f },
72     { 5.0f,  25.0f },  // remove if collinear diagonal points are not concave
73     { 4.0f,  50.0f },
74     { -4.0f,  50.0f }
75 };
76 // degenerate
77 const SkPoint gPoints5[] = {
78     { -0.025f, -0.025f },
79     { 0.025f, -0.025f },
80     { 0.025f,  0.025f },
81     { -0.025f,  0.025f }
82 };
83 // Triangle in which the first point should fuse with last
84 const SkPoint gPoints6[] = {
85     { -20.0f, -13.0f },
86     { -20.0f, -13.05f },
87     { 20.0f, -13.0f },
88     { 20.0f,  27.0f }
89 };
90 // thin rect with colinear lines
91 const SkPoint gPoints7[] = {
92     { -10.0f, -50.0f },
93     { 10.0f, -50.0f },
94     { 10.0f, -25.0f },
95     { 10.0f,   0.0f },
96     { 10.0f,  25.0f },
97     { 10.0f,  50.0f },
98     { -10.0f,  50.0f }
99 };
100 // capped teardrop
101 const SkPoint gPoints8[] = {
102     { 50.00f,  50.00f },
103     { 0.00f,  50.00f },
104     { -15.45f,  47.55f },
105     { -29.39f,  40.45f },
106     { -40.45f,  29.39f },
107     { -47.55f,  15.45f },
108     { -50.00f,   0.00f },
109     { -47.55f, -15.45f },
110     { -40.45f, -29.39f },
111     { -29.39f, -40.45f },
112     { -15.45f, -47.55f },
113     { 0.00f, -50.00f },
114     { 50.00f, -50.00f }
115 };
116 // teardrop
117 const SkPoint gPoints9[] = {
118     { 4.39f,  40.45f },
119     { -9.55f,  47.55f },
120     { -25.00f,  50.00f },
121     { -40.45f,  47.55f },
122     { -54.39f,  40.45f },
123     { -65.45f,  29.39f },
124     { -72.55f,  15.45f },
125     { -75.00f,   0.00f },
126     { -72.55f, -15.45f },
127     { -65.45f, -29.39f },
128     { -54.39f, -40.45f },
129     { -40.45f, -47.55f },
130     { -25.0f,  -50.0f },
131     { -9.55f, -47.55f },
132     { 4.39f, -40.45f },
133     { 75.00f,   0.00f }
134 };
135 // clipped triangle
136 const SkPoint gPoints10[] = {
137     { -10.0f, -50.0f },
138     { 10.0f, -50.0f },
139     { 50.0f,  31.0f },
140     { 40.0f,  50.0f },
141     { -40.0f,  50.0f },
142     { -50.0f,  31.0f },
143 };
144 
145 const SkPoint* gPoints[] = {
146     gPoints0, gPoints1, gPoints2, gPoints3, gPoints4, gPoints5, gPoints6,
147     gPoints7, gPoints8, gPoints9, gPoints10,
148 };
149 
150 const size_t gSizes[] = {
151     SK_ARRAY_COUNT(gPoints0),
152     SK_ARRAY_COUNT(gPoints1),
153     SK_ARRAY_COUNT(gPoints2),
154     SK_ARRAY_COUNT(gPoints3),
155     SK_ARRAY_COUNT(gPoints4),
156     SK_ARRAY_COUNT(gPoints5),
157     SK_ARRAY_COUNT(gPoints6),
158     SK_ARRAY_COUNT(gPoints7),
159     SK_ARRAY_COUNT(gPoints8),
160     SK_ARRAY_COUNT(gPoints9),
161     SK_ARRAY_COUNT(gPoints10),
162 };
163 static_assert(SK_ARRAY_COUNT(gSizes) == SK_ARRAY_COUNT(gPoints), "array_mismatch");
164 }
165 
166 namespace skiagm {
167 
168 // This GM is intended to exercise Ganesh's handling of convex line-only
169 // paths
170 class ConvexLineOnlyPathsGM : public GM {
171 public:
ConvexLineOnlyPathsGM(bool doStrokeAndFill)172     ConvexLineOnlyPathsGM(bool doStrokeAndFill) : fDoStrokeAndFill(doStrokeAndFill) {
173         this->setBGColor(0xFFFFFFFF);
174     }
175 
176 protected:
onShortName()177     SkString onShortName() override {
178         if (fDoStrokeAndFill) {
179             return SkString("convex-lineonly-paths-stroke-and-fill");
180         }
181         return SkString("convex-lineonly-paths");
182     }
onISize()183     SkISize onISize() override { return SkISize::Make(kGMWidth, kGMHeight); }
runAsBench() const184     bool runAsBench() const override { return true; }
185 
GetPath(int index,SkPath::Direction dir)186     static SkPath GetPath(int index, SkPath::Direction dir) {
187         std::unique_ptr<SkPoint[]> data(nullptr);
188         const SkPoint* points;
189         int numPts;
190         if (index < (int) SK_ARRAY_COUNT(ConvexLineOnlyData::gPoints)) {
191             // manually specified
192             points = ConvexLineOnlyData::gPoints[index];
193             numPts = (int)ConvexLineOnlyData::gSizes[index];
194         } else {
195             // procedurally generated
196             SkScalar width = kMaxPathHeight/2;
197             SkScalar height = kMaxPathHeight/2;
198             switch (index-SK_ARRAY_COUNT(ConvexLineOnlyData::gPoints)) {
199             case 0:
200                 numPts = 3;
201                 break;
202             case 1:
203                 numPts = 4;
204                 break;
205             case 2:
206                 numPts = 5;
207                 break;
208             case 3:             // squashed pentagon
209                 numPts = 5;
210                 width = kMaxPathHeight/5;
211                 break;
212             case 4:
213                 numPts = 6;
214                 break;
215             case 5:
216                 numPts = 8;
217                 break;
218             case 6:              // squashed octogon
219                 numPts = 8;
220                 width = kMaxPathHeight/5;
221                 break;
222             case 7:
223                 numPts = 20;
224                 break;
225             case 8:
226                 numPts = 100;
227                 break;
228             default:
229                 numPts = 3;
230                 break;
231             }
232 
233             data.reset(new SkPoint[numPts]);
234 
235             create_ngon(numPts, data.get(), width, height);
236             points = data.get();
237         }
238 
239         SkPath path;
240 
241         if (SkPath::kCW_Direction == dir) {
242             path.moveTo(points[0]);
243             for (int i = 1; i < numPts; ++i) {
244                 path.lineTo(points[i]);
245             }
246         } else {
247             path.moveTo(points[numPts-1]);
248             for (int i = numPts-2; i >= 0; --i) {
249                 path.lineTo(points[i]);
250             }
251         }
252 
253         path.close();
254 #ifdef SK_DEBUG
255         // Each path this method returns should be convex, only composed of
256         // lines, wound the right direction, and short enough to fit in one
257         // of the GMs rows.
258         SkASSERT(path.isConvex());
259         SkASSERT(SkPath::kLine_SegmentMask == path.getSegmentMasks());
260         SkPathPriv::FirstDirection actualDir;
261         SkASSERT(SkPathPriv::CheapComputeFirstDirection(path, &actualDir));
262         SkASSERT(SkPathPriv::AsFirstDirection(dir) == actualDir);
263         SkRect bounds = path.getBounds();
264         SkASSERT(SkScalarNearlyEqual(bounds.centerX(), 0.0f));
265         SkASSERT(bounds.height() <= kMaxPathHeight);
266 #endif
267         return path;
268     }
269 
270     // Draw a single path several times, shrinking it, flipping its direction
271     // and changing its start vertex each time.
drawPath(SkCanvas * canvas,int index,SkPoint * offset)272     void drawPath(SkCanvas* canvas, int index, SkPoint* offset) {
273 
274         SkPoint center;
275         {
276             SkPath path = GetPath(index, SkPath::kCW_Direction);
277             if (offset->fX+path.getBounds().width() > kGMWidth) {
278                 offset->fX = 0;
279                 offset->fY += kMaxPathHeight;
280                 if (fDoStrokeAndFill) {
281                     offset->fX += kStrokeWidth / 2.0f;
282                     offset->fY += kStrokeWidth / 2.0f;
283                 }
284             }
285             center = { offset->fX + SkScalarHalf(path.getBounds().width()), offset->fY};
286             offset->fX += path.getBounds().width();
287             if (fDoStrokeAndFill) {
288                 offset->fX += kStrokeWidth;
289             }
290         }
291 
292         const SkColor colors[2] = { SK_ColorBLACK, SK_ColorWHITE };
293         const SkPath::Direction dirs[2] = { SkPath::kCW_Direction, SkPath::kCCW_Direction };
294         const float scales[] = { 1.0f, 0.75f, 0.5f, 0.25f, 0.1f, 0.01f, 0.001f };
295         const SkPaint::Join joins[3] = { SkPaint::kRound_Join,
296                                          SkPaint::kBevel_Join,
297                                          SkPaint::kMiter_Join };
298 
299         SkPaint paint;
300         paint.setAntiAlias(true);
301 
302         for (size_t i = 0; i < SK_ARRAY_COUNT(scales); ++i) {
303             SkPath path = GetPath(index, dirs[i%2]);
304             if (fDoStrokeAndFill) {
305                 paint.setStyle(SkPaint::kStrokeAndFill_Style);
306                 paint.setStrokeJoin(joins[i%3]);
307                 paint.setStrokeWidth(SkIntToScalar(kStrokeWidth));
308             }
309 
310             canvas->save();
311                 canvas->translate(center.fX, center.fY);
312                 canvas->scale(scales[i], scales[i]);
313                 paint.setColor(colors[i%2]);
314                 canvas->drawPath(path, paint);
315             canvas->restore();
316         }
317     }
318 
onDraw(SkCanvas * canvas)319     void onDraw(SkCanvas* canvas) override {
320         // the right edge of the last drawn path
321         SkPoint offset = { 0, SkScalarHalf(kMaxPathHeight) };
322         if (fDoStrokeAndFill) {
323             offset.fX += kStrokeWidth / 2.0f;
324             offset.fY += kStrokeWidth / 2.0f;
325         }
326 
327         for (int i = 0; i < kNumPaths; ++i) {
328             this->drawPath(canvas, i, &offset);
329         }
330 
331         {
332             // Repro for crbug.com/472723 (Missing AA on portions of graphic with GPU rasterization)
333 
334             SkPaint p;
335             p.setAntiAlias(true);
336             if (fDoStrokeAndFill) {
337                 p.setStyle(SkPaint::kStrokeAndFill_Style);
338                 p.setStrokeJoin(SkPaint::kMiter_Join);
339                 p.setStrokeWidth(SkIntToScalar(kStrokeWidth));
340             }
341 
342             SkPath p1;
343             p1.moveTo(60.8522949f, 364.671021f);
344             p1.lineTo(59.4380493f, 364.671021f);
345             p1.lineTo(385.414276f, 690.647217f);
346             p1.lineTo(386.121399f, 689.940125f);
347             canvas->save();
348             canvas->translate(356.0f, 50.0f);
349             canvas->drawPath(p1, p);
350             canvas->restore();
351 
352             // Repro for crbug.com/869172 (SVG path incorrectly simplified when using GPU
353             // Rasterization). This will only draw anything in the stroke-and-fill version.
354             SkPath p2;
355             p2.moveTo(10.f, 0.f);
356             p2.lineTo(38.f, 0.f);
357             p2.lineTo(66.f, 0.f);
358             p2.lineTo(94.f, 0.f);
359             p2.lineTo(122.f, 0.f);
360             p2.lineTo(150.f, 0.f);
361             p2.lineTo(150.f, 0.f);
362             p2.lineTo(122.f, 0.f);
363             p2.lineTo(94.f, 0.f);
364             p2.lineTo(66.f, 0.f);
365             p2.lineTo(38.f, 0.f);
366             p2.lineTo(10.f, 0.f);
367             p2.close();
368             canvas->save();
369             canvas->translate(0.0f, 500.0f);
370             canvas->drawPath(p2, p);
371             canvas->restore();
372 
373             // Repro for crbug.com/856137. This path previously caused GrAAConvexTessellator to turn
374             // inset rings into outsets when adjacent bisector angles converged outside the previous
375             // ring due to accumulated error.
376             SkPath p3;
377             p3.setFillType(SkPath::kEvenOdd_FillType);
378             p3.moveTo(1184.96f, 982.557f);
379             p3.lineTo(1183.71f, 982.865f);
380             p3.lineTo(1180.99f, 982.734f);
381             p3.lineTo(1178.5f, 981.541f);
382             p3.lineTo(1176.35f, 979.367f);
383             p3.lineTo(1178.94f, 938.854f);
384             p3.lineTo(1181.35f, 936.038f);
385             p3.lineTo(1183.96f, 934.117f);
386             p3.lineTo(1186.67f, 933.195f);
387             p3.lineTo(1189.36f, 933.342f);
388             p3.lineTo(1191.58f, 934.38f);
389             p3.close();
390             canvas->save();
391             SkMatrix m;
392             m.setAll(0.0893210843f, 0, 79.1197586f, 0, 0.0893210843f, 300, 0, 0, 1);
393             canvas->concat(m);
394             canvas->drawPath(p3, p);
395             canvas->restore();
396         }
397     }
398 
399 private:
400     static constexpr int kStrokeWidth   = 10;
401     static constexpr int kNumPaths      = 20;
402     static constexpr int kMaxPathHeight = 100;
403     static constexpr int kGMWidth       = 512;
404     static constexpr int kGMHeight      = 512;
405 
406     bool fDoStrokeAndFill;
407 
408     typedef GM INHERITED;
409 };
410 
411 //////////////////////////////////////////////////////////////////////////////
412 
413 DEF_GM(return new ConvexLineOnlyPathsGM(false);)
414 DEF_GM(return new ConvexLineOnlyPathsGM(true);)
415 }
416