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