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/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkPath.h"
13 #include "include/core/SkPoint.h"
14 #include "include/core/SkRect.h"
15 #include "include/core/SkScalar.h"
16 #include "include/core/SkSize.h"
17 #include "include/core/SkString.h"
18 #include "include/core/SkTypes.h"
19 #include "include/private/SkTDArray.h"
20 #include "src/utils/SkPolyUtils.h"
21 #include "tools/ToolUtils.h"
22 
23 #include <functional>
24 #include <memory>
25 
create_ngon(int n,SkPoint * pts,SkScalar w,SkScalar h,SkPathDirection dir)26 static void create_ngon(int n, SkPoint* pts, SkScalar w, SkScalar h, SkPathDirection dir) {
27     float angleStep = 360.0f / n, angle = 0.0f;
28     if ((n % 2) == 1) {
29         angle = angleStep/2.0f;
30     }
31     if (SkPathDirection::kCCW == dir) {
32         angle = -angle;
33         angleStep = -angleStep;
34     }
35 
36     for (int i = 0; i < n; ++i) {
37         pts[i].fX = -SkScalarSin(SkDegreesToRadians(angle)) * w;
38         pts[i].fY =  SkScalarCos(SkDegreesToRadians(angle)) * h;
39         angle += angleStep;
40     }
41 }
42 
43 namespace PolygonOffsetData {
44 // narrow rect
45 const SkPoint gPoints0[] = {
46     { -1.5f, -50.0f },
47     { 1.5f, -50.0f },
48     { 1.5f,  50.0f },
49     { -1.5f,  50.0f }
50 };
51 // narrow rect on an angle
52 const SkPoint gPoints1[] = {
53     { -50.0f, -49.0f },
54     { -49.0f, -50.0f },
55     { 50.0f,  49.0f },
56     { 49.0f,  50.0f }
57 };
58 // trap - narrow on top - wide on bottom
59 const SkPoint gPoints2[] = {
60     { -10.0f, -50.0f },
61     { 10.0f, -50.0f },
62     { 50.0f,  50.0f },
63     { -50.0f,  50.0f }
64 };
65 // wide skewed rect
66 const SkPoint gPoints3[] = {
67     { -50.0f, -50.0f },
68     { 0.0f, -50.0f },
69     { 50.0f,  50.0f },
70     { 0.0f,  50.0f }
71 };
72 // thin rect with colinear-ish lines
73 const SkPoint gPoints4[] = {
74     { -6.0f, -50.0f },
75     { 4.0f, -50.0f },
76     { 5.0f, -25.0f },
77     { 6.0f,   0.0f },
78     { 5.0f,  25.0f },
79     { 4.0f,  50.0f },
80     { -4.0f,  50.0f }
81 };
82 // degenerate
83 const SkPoint gPoints5[] = {
84     { -0.025f, -0.025f },
85     { 0.025f, -0.025f },
86     { 0.025f,  0.025f },
87     { -0.025f,  0.025f }
88 };
89 // Quad with near coincident point
90 const SkPoint gPoints6[] = {
91     { -20.0f, -13.0f },
92     { -20.0f, -13.05f },
93     { 20.0f, -13.0f },
94     { 20.0f,  27.0f }
95 };
96 // thin rect with colinear lines
97 const SkPoint gPoints7[] = {
98     { -10.0f, -50.0f },
99     { 10.0f, -50.0f },
100     { 10.0f, -20.0f },
101     { 10.0f,   0.0f },
102     { 10.0f,  35.0f },
103     { 10.0f,  50.0f },
104     { -10.0f,  50.0f }
105 };
106 // capped teardrop
107 const SkPoint gPoints8[] = {
108     { 50.00f,  50.00f },
109     { 0.00f,  50.00f },
110     { -15.45f,  47.55f },
111     { -29.39f,  40.45f },
112     { -40.45f,  29.39f },
113     { -47.55f,  15.45f },
114     { -50.00f,   0.00f },
115     { -47.55f, -15.45f },
116     { -40.45f, -29.39f },
117     { -29.39f, -40.45f },
118     { -15.45f, -47.55f },
119     { 0.00f, -50.00f },
120     { 50.00f, -50.00f }
121 };
122 // teardrop
123 const SkPoint gPoints9[] = {
124     { 4.39f,  40.45f },
125     { -9.55f,  47.55f },
126     { -25.00f,  50.00f },
127     { -40.45f,  47.55f },
128     { -54.39f,  40.45f },
129     { -65.45f,  29.39f },
130     { -72.55f,  15.45f },
131     { -75.00f,   0.00f },
132     { -72.55f, -15.45f },
133     { -65.45f, -29.39f },
134     { -54.39f, -40.45f },
135     { -40.45f, -47.55f },
136     { -25.0f,  -50.0f },
137     { -9.55f, -47.55f },
138     { 4.39f, -40.45f },
139     { 75.00f,   0.00f }
140 };
141 // clipped triangle
142 const SkPoint gPoints10[] = {
143     { -10.0f, -50.0f },
144     { 10.0f, -50.0f },
145     { 50.0f,  31.0f },
146     { 40.0f,  50.0f },
147     { -40.0f,  50.0f },
148     { -50.0f,  31.0f },
149 };
150 
151 // tab
152 const SkPoint gPoints11[] = {
153     { -45, -25 },
154     { 45, -25 },
155     { 45, 25 },
156     { 20, 25 },
157     { 19.6157f, 25.f + 3.9018f },
158     { 18.4776f, 25.f + 7.6537f },
159     { 16.6294f, 25.f + 11.1114f },
160     { 14.1421f, 25.f + 14.1421f },
161     { 11.1114f, 25.f + 16.6294f },
162     { 7.6537f, 25.f + 18.4776f },
163     { 3.9018f, 25.f + 19.6157f },
164     { 0, 45.f },
165     { -3.9018f, 25.f + 19.6157f },
166     { -7.6537f, 25.f + 18.4776f },
167     { -11.1114f, 25.f + 16.6294f },
168     { -14.1421f, 25.f + 14.1421f },
169     { -16.6294f, 25.f + 11.1114f },
170     { -18.4776f, 25.f + 7.6537f },
171     { -19.6157f, 25.f + 3.9018f },
172     { -20, 25 },
173     { -45, 25 }
174 };
175 
176 // star of david
177 const SkPoint gPoints12[] = {
178     { 0.0f, -50.0f },
179     { 14.43f, -25.0f },
180     { 43.30f, -25.0f },
181     { 28.86f, 0.0f },
182     { 43.30f, 25.0f },
183     { 14.43f, 25.0f },
184     { 0.0f, 50.0f },
185     { -14.43f, 25.0f },
186     { -43.30f, 25.0f },
187     { -28.86f, 0.0f },
188     { -43.30f, -25.0f },
189     { -14.43f, -25.0f },
190 };
191 
192 // notch
193 const SkScalar kBottom = 25.f;
194 const SkPoint gPoints13[] = {
195     { -50, kBottom - 50.f },
196     { 50, kBottom - 50.f },
197     { 50, kBottom },
198     { 20, kBottom },
199     { 19.6157f, kBottom - 3.9018f },
200     { 18.4776f, kBottom - 7.6537f },
201     { 16.6294f, kBottom - 11.1114f },
202     { 14.1421f, kBottom - 14.1421f },
203     { 11.1114f, kBottom - 16.6294f },
204     { 7.6537f, kBottom - 18.4776f },
205     { 3.9018f, kBottom - 19.6157f },
206     { 0, kBottom - 20.f },
207     { -3.9018f, kBottom - 19.6157f },
208     { -7.6537f, kBottom - 18.4776f },
209     { -11.1114f, kBottom - 16.6294f },
210     { -14.1421f, kBottom - 14.1421f },
211     { -16.6294f, kBottom - 11.1114f },
212     { -18.4776f, kBottom - 7.6537f },
213     { -19.6157f, kBottom - 3.9018f },
214     { -20, kBottom },
215     { -50, kBottom }
216 };
217 
218 // crown
219 const SkPoint gPoints14[] = {
220     { -40, -39 },
221     { 40, -39 },
222     { 40, -20 },
223     { 30, 40 },
224     { 20, -20 },
225     { 10, 40 },
226     { 0, -20 },
227     { -10, 40 },
228     { -20, -20 },
229     { -30, 40 },
230     { -40, -20 }
231 };
232 
233 // dumbbell
234 const SkPoint gPoints15[] = {
235     { -26, -3 },
236     { -24, -6.2f },
237     { -22.5f, -8 },
238     { -20, -9.9f },
239     { -17.5f, -10.3f },
240     { -15, -10.9f },
241     { -12.5f, -10.2f },
242     { -10, -9.7f },
243     { -7.5f, -8.1f },
244     { -5, -7.7f },
245     { -2.5f, -7.4f },
246     { 0, -7.7f },
247     { 3, -9 },
248     { 6.5f, -11.5f },
249     { 10.6f, -14 },
250     { 14, -15.2f },
251     { 17, -15.5f },
252     { 20, -15.2f },
253     { 23.4f, -14 },
254     { 27.5f, -11.5f },
255     { 30, -8 },
256     { 32, -4 },
257     { 32.5f, 0 },
258     { 32, 4 },
259     { 30, 8 },
260     { 27.5f, 11.5f },
261     { 23.4f, 14 },
262     { 20, 15.2f },
263     { 17, 15.5f },
264     { 14, 15.2f },
265     { 10.6f, 14 },
266     { 6.5f, 11.5f },
267     { 3, 9 },
268     { 0, 7.7f },
269     { -2.5f, 7.4f },
270     { -5, 7.7f },
271     { -7.5f, 8.1f },
272     { -10, 9.7f },
273     { -12.5f, 10.2f },
274     { -15, 10.9f },
275     { -17.5f, 10.3f },
276     { -20, 9.9f },
277     { -22.5f, 8 },
278     { -24, 6.2f },
279     { -26, 3 },
280     { -26.5f, 0 }
281 };
282 
283 // truncated dumbbell
284 // (checks winding computation in OffsetSimplePolygon)
285 const SkPoint gPoints16[] = {
286     { -15 + 3, -9 },
287     { -15 + 6.5f, -11.5f },
288     { -15 + 10.6f, -14 },
289     { -15 + 14, -15.2f },
290     { -15 + 17, -15.5f },
291     { -15 + 20, -15.2f },
292     { -15 + 23.4f, -14 },
293     { -15 + 27.5f, -11.5f },
294     { -15 + 30, -8 },
295     { -15 + 32, -4 },
296     { -15 + 32.5f, 0 },
297     { -15 + 32, 4 },
298     { -15 + 30, 8 },
299     { -15 + 27.5f, 11.5f },
300     { -15 + 23.4f, 14 },
301     { -15 + 20, 15.2f },
302     { -15 + 17, 15.5f },
303     { -15 + 14, 15.2f },
304     { -15 + 10.6f, 14 },
305     { -15 + 6.5f, 11.5f },
306     { -15 + 3, 9 },
307 };
308 
309 // square notch
310 // (to detect segment-segment intersection)
311 const SkPoint gPoints17[] = {
312     { -50, kBottom - 50.f },
313     { 50, kBottom - 50.f },
314     { 50, kBottom },
315     { 20, kBottom },
316     { 20, kBottom - 20.f },
317     { -20, kBottom - 20.f },
318     { -20, kBottom },
319     { -50, kBottom }
320 };
321 
322 // box with Peano curve
323 const SkPoint gPoints18[] = {
324     { 0, 0 },
325     { 0, -12 },
326     { -6, -12 },
327     { -6, 0 },
328     { -12, 0 },
329     { -12, -12},
330     { -18, -12},
331     { -18, 18},
332     { -12, 18},
333     {-12, 6},
334     {-6, 6},
335     {-6, 36},
336     {-12, 36},
337     {-12, 24},
338     {-18, 24},
339     {-18, 36},
340     {-24, 36},
341     {-24, 24},
342     {-30, 24},
343     {-30, 36},
344     {-36, 36},
345     {-36, 6},
346     {-30, 6},
347     {-30, 18},
348     {-24, 18},
349     {-24, -12},
350     {-30, -12},
351     {-30, 0},
352     {-36, 0},
353     {-36, -36},
354     {36, -36},
355     {36, 36},
356     {12, 36},
357     {12, 24},
358     {6, 24},
359     {6, 36},
360     {0, 36},
361     {0, 6},
362     {6, 6},
363     {6, 18},
364     {12, 18},
365     {12, -12},
366     {6, -12},
367     {6, 0}
368 };
369 
370 
371 const SkPoint* gConvexPoints[] = {
372     gPoints0, gPoints1, gPoints2, gPoints3, gPoints4, gPoints5, gPoints6,
373     gPoints7, gPoints8, gPoints9, gPoints10,
374 };
375 
376 const size_t gConvexSizes[] = {
377     SK_ARRAY_COUNT(gPoints0),
378     SK_ARRAY_COUNT(gPoints1),
379     SK_ARRAY_COUNT(gPoints2),
380     SK_ARRAY_COUNT(gPoints3),
381     SK_ARRAY_COUNT(gPoints4),
382     SK_ARRAY_COUNT(gPoints5),
383     SK_ARRAY_COUNT(gPoints6),
384     SK_ARRAY_COUNT(gPoints7),
385     SK_ARRAY_COUNT(gPoints8),
386     SK_ARRAY_COUNT(gPoints9),
387     SK_ARRAY_COUNT(gPoints10),
388 };
389 static_assert(SK_ARRAY_COUNT(gConvexSizes) == SK_ARRAY_COUNT(gConvexPoints), "array_mismatch");
390 
391 const SkPoint* gSimplePoints[] = {
392     gPoints0, gPoints1, gPoints2, gPoints4, gPoints5, gPoints7,
393     gPoints8, gPoints11, gPoints12, gPoints13, gPoints14, gPoints15,
394     gPoints16, gPoints17, gPoints18,
395 };
396 
397 const size_t gSimpleSizes[] = {
398     SK_ARRAY_COUNT(gPoints0),
399     SK_ARRAY_COUNT(gPoints1),
400     SK_ARRAY_COUNT(gPoints2),
401     SK_ARRAY_COUNT(gPoints4),
402     SK_ARRAY_COUNT(gPoints5),
403     SK_ARRAY_COUNT(gPoints7),
404     SK_ARRAY_COUNT(gPoints8),
405     SK_ARRAY_COUNT(gPoints11),
406     SK_ARRAY_COUNT(gPoints12),
407     SK_ARRAY_COUNT(gPoints13),
408     SK_ARRAY_COUNT(gPoints14),
409     SK_ARRAY_COUNT(gPoints15),
410     SK_ARRAY_COUNT(gPoints16),
411     SK_ARRAY_COUNT(gPoints17),
412     SK_ARRAY_COUNT(gPoints18),
413 };
414 static_assert(SK_ARRAY_COUNT(gSimpleSizes) == SK_ARRAY_COUNT(gSimplePoints), "array_mismatch");
415 
416 }  // namespace PolygonOffsetData
417 
418 namespace skiagm {
419 
420 // This GM is intended to exercise the offsetting of polygons
421 // When fVariableOffset is true it will skew the offset by x,
422 // to test perspective and other variable offset functions
423 class PolygonOffsetGM : public GM {
424 public:
PolygonOffsetGM(bool convexOnly)425     PolygonOffsetGM(bool convexOnly)
426         : fConvexOnly(convexOnly) {
427         this->setBGColor(0xFFFFFFFF);
428     }
429 
430 protected:
onShortName()431     SkString onShortName() override {
432         if (fConvexOnly) {
433             return SkString("convex-polygon-inset");
434         } else {
435             return SkString("simple-polygon-offset");
436         }
437     }
onISize()438     SkISize onISize() override { return SkISize::Make(kGMWidth, kGMHeight); }
runAsBench() const439     bool runAsBench() const override { return true; }
440 
GetConvexPolygon(int index,SkPathDirection dir,std::unique_ptr<SkPoint[]> * data,int * numPts)441     static void GetConvexPolygon(int index, SkPathDirection dir,
442                                  std::unique_ptr<SkPoint[]>* data, int* numPts) {
443         if (index < (int)SK_ARRAY_COUNT(PolygonOffsetData::gConvexPoints)) {
444             // manually specified
445             *numPts = (int)PolygonOffsetData::gConvexSizes[index];
446             *data = std::make_unique<SkPoint[]>(*numPts);
447             if (SkPathDirection::kCW == dir) {
448                 for (int i = 0; i < *numPts; ++i) {
449                     (*data)[i] = PolygonOffsetData::gConvexPoints[index][i];
450                 }
451             } else {
452                 for (int i = 0; i < *numPts; ++i) {
453                     (*data)[i] = PolygonOffsetData::gConvexPoints[index][*numPts - i - 1];
454                 }
455             }
456         } else {
457             // procedurally generated
458             SkScalar width = kMaxPathHeight / 2;
459             SkScalar height = kMaxPathHeight / 2;
460             int numPtsArray[] = { 3, 4, 5, 5, 6, 8, 8, 20, 100 };
461 
462             size_t arrayIndex = index - SK_ARRAY_COUNT(PolygonOffsetData::gConvexPoints);
463             SkASSERT(arrayIndex < SK_ARRAY_COUNT(numPtsArray));
464             *numPts = numPtsArray[arrayIndex];
465             if (arrayIndex == 3 || arrayIndex == 6) {
466                 // squashed pentagon and octagon
467                 width = kMaxPathHeight / 5;
468             }
469 
470             *data = std::make_unique<SkPoint[]>(*numPts);
471 
472             create_ngon(*numPts, data->get(), width, height, dir);
473         }
474     }
475 
GetSimplePolygon(int index,SkPathDirection dir,std::unique_ptr<SkPoint[]> * data,int * numPts)476     static void GetSimplePolygon(int index, SkPathDirection dir,
477                                  std::unique_ptr<SkPoint[]>* data, int* numPts) {
478         if (index < (int)SK_ARRAY_COUNT(PolygonOffsetData::gSimplePoints)) {
479             // manually specified
480             *numPts = (int)PolygonOffsetData::gSimpleSizes[index];
481             *data = std::make_unique<SkPoint[]>(*numPts);
482             if (SkPathDirection::kCW == dir) {
483                 for (int i = 0; i < *numPts; ++i) {
484                     (*data)[i] = PolygonOffsetData::gSimplePoints[index][i];
485                 }
486             } else {
487                 for (int i = 0; i < *numPts; ++i) {
488                     (*data)[i] = PolygonOffsetData::gSimplePoints[index][*numPts - i - 1];
489                 }
490             }
491         } else {
492             // procedurally generated
493             SkScalar width = kMaxPathHeight / 2;
494             SkScalar height = kMaxPathHeight / 2;
495             int numPtsArray[] = { 5, 7, 8, 20, 100 };
496 
497             size_t arrayIndex = index - SK_ARRAY_COUNT(PolygonOffsetData::gSimplePoints);
498             arrayIndex = std::min(arrayIndex, SK_ARRAY_COUNT(numPtsArray) - 1);
499             SkASSERT(arrayIndex < SK_ARRAY_COUNT(numPtsArray));
500             *numPts = numPtsArray[arrayIndex];
501             // squash horizontally
502             width = kMaxPathHeight / 5;
503 
504             *data = std::make_unique<SkPoint[]>(*numPts);
505 
506             create_ngon(*numPts, data->get(), width, height, dir);
507         }
508     }
509     // Draw a single polygon with insets and potentially outsets
drawPolygon(SkCanvas * canvas,int index,SkPoint * offset)510     void drawPolygon(SkCanvas* canvas, int index, SkPoint* offset) {
511 
512         SkPoint center;
513         SkRect bounds;
514         {
515             std::unique_ptr<SkPoint[]> data(nullptr);
516             int numPts;
517             if (fConvexOnly) {
518                 GetConvexPolygon(index, SkPathDirection::kCW, &data, &numPts);
519             } else {
520                 GetSimplePolygon(index, SkPathDirection::kCW, &data, &numPts);
521             }
522             bounds.setBounds(data.get(), numPts);
523             if (!fConvexOnly) {
524                 bounds.outset(kMaxOutset, kMaxOutset);
525             }
526             if (offset->fX + bounds.width() > kGMWidth) {
527                 offset->fX = 0;
528                 offset->fY += kMaxPathHeight;
529             }
530             center = { offset->fX + SkScalarHalf(bounds.width()), offset->fY };
531             offset->fX += bounds.width();
532         }
533 
534         const SkPathDirection dirs[2] = { SkPathDirection::kCW, SkPathDirection::kCCW };
535         const float insets[] = { 5, 10, 15, 20, 25, 30, 35, 40 };
536         const float offsets[] = { 2, 5, 9, 14, 20, 27, 35, 44, -2, -5, -9 };
537         const SkColor colors[] = { 0xFF901313, 0xFF8D6214, 0xFF698B14, 0xFF1C8914,
538                                    0xFF148755, 0xFF146C84, 0xFF142482, 0xFF4A1480,
539                                    0xFF901313, 0xFF8D6214, 0xFF698B14 };
540 
541         SkPaint paint;
542         paint.setAntiAlias(true);
543         paint.setStyle(SkPaint::kStroke_Style);
544         paint.setStrokeWidth(1);
545 
546         std::unique_ptr<SkPoint[]> data(nullptr);
547         int numPts;
548         if (fConvexOnly) {
549             GetConvexPolygon(index, dirs[index % 2], &data, &numPts);
550         } else {
551             GetSimplePolygon(index, dirs[index % 2], &data, &numPts);
552         }
553 
554         {
555             SkPath path;
556             path.moveTo(data.get()[0]);
557             for (int i = 1; i < numPts; ++i) {
558                 path.lineTo(data.get()[i]);
559             }
560             path.close();
561             canvas->save();
562             canvas->translate(center.fX, center.fY);
563             canvas->drawPath(path, paint);
564             canvas->restore();
565         }
566 
567         SkTDArray<SkPoint> offsetPoly;
568         size_t count = fConvexOnly ? SK_ARRAY_COUNT(insets) : SK_ARRAY_COUNT(offsets);
569         for (size_t i = 0; i < count; ++i) {
570             SkScalar offset = fConvexOnly ? insets[i] : offsets[i];
571             std::function<SkScalar(const SkPoint&)> offsetFunc;
572 
573             bool result;
574             if (fConvexOnly) {
575                 result = SkInsetConvexPolygon(data.get(), numPts, offset, &offsetPoly);
576             } else {
577                 SkRect bounds;
578                 bounds.setBoundsCheck(data.get(), numPts);
579                 result = SkOffsetSimplePolygon(data.get(), numPts, bounds, offset, &offsetPoly);
580             }
581             if (result) {
582                 SkPath path;
583                 path.moveTo(offsetPoly[0]);
584                 for (int i = 1; i < offsetPoly.count(); ++i) {
585                     path.lineTo(offsetPoly[i]);
586                 }
587                 path.close();
588 
589                 paint.setColor(ToolUtils::color_to_565(colors[i]));
590                 canvas->save();
591                 canvas->translate(center.fX, center.fY);
592                 canvas->drawPath(path, paint);
593                 canvas->restore();
594             }
595         }
596     }
597 
onDraw(SkCanvas * canvas)598     void onDraw(SkCanvas* canvas) override {
599         // the right edge of the last drawn path
600         SkPoint offset = { 0, SkScalarHalf(kMaxPathHeight) };
601         if (!fConvexOnly) {
602             offset.fY += kMaxOutset;
603         }
604 
605         for (int i = 0; i < kNumPaths; ++i) {
606             this->drawPolygon(canvas, i, &offset);
607         }
608     }
609 
610 private:
611     static constexpr int kNumPaths = 20;
612     static constexpr int kMaxPathHeight = 100;
613     static constexpr int kMaxOutset = 16;
614     static constexpr int kGMWidth = 512;
615     static constexpr int kGMHeight = 512;
616 
617     bool fConvexOnly;
618 
619     using INHERITED = GM;
620 };
621 
622 //////////////////////////////////////////////////////////////////////////////
623 
624 DEF_GM(return new PolygonOffsetGM(true);)
625 DEF_GM(return new PolygonOffsetGM(false);)
626 }  // namespace skiagm
627