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