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