blob: 55aebda621e22dbe20284ceafb286cf61d3f99ee [file] [log] [blame]
caryclark64022c12016-05-27 05:13:26 -07001/*
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 "SampleCode.h"
Florin Malitaab244f02017-05-03 19:16:58 +00009#include "SkBitmap.h"
caryclark64022c12016-05-27 05:13:26 -070010#include "SkCanvas.h"
11#include "SkGeometry.h"
12#include "SkIntersections.h"
13#include "SkOpEdgeBuilder.h"
14// #include "SkPathOpsSimplifyAA.h"
15// #include "SkPathStroker.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050016#include "SkPointPriv.h"
caryclark64022c12016-05-27 05:13:26 -070017#include "SkView.h"
18
19#if 0
20void SkStrokeSegment::dump() const {
21 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
22 if (SkPath::kQuad_Verb == fVerb) {
23 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
24 }
25 SkDebugf("}}");
26#ifdef SK_DEBUG
27 SkDebugf(" id=%d", fDebugID);
28#endif
29 SkDebugf("\n");
30}
31
32void SkStrokeSegment::dumpAll() const {
33 const SkStrokeSegment* segment = this;
34 while (segment) {
35 segment->dump();
36 segment = segment->fNext;
37 }
38}
39
40void SkStrokeTriple::dump() const {
41 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
42 if (SkPath::kQuad_Verb <= fVerb) {
43 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
44 }
45 if (SkPath::kCubic_Verb == fVerb) {
46 SkDebugf(", {%1.9g,%1.9g}", fPts[3].fX, fPts[3].fY);
47 } else if (SkPath::kConic_Verb == fVerb) {
48 SkDebugf(", %1.9g", weight());
49 }
50 SkDebugf("}}");
51#ifdef SK_DEBUG
52 SkDebugf(" triple id=%d", fDebugID);
53#endif
54 SkDebugf("\ninner:\n");
55 fInner->dumpAll();
56 SkDebugf("outer:\n");
57 fOuter->dumpAll();
58 SkDebugf("join:\n");
59 fJoin->dumpAll();
60}
61
62void SkStrokeTriple::dumpAll() const {
63 const SkStrokeTriple* triple = this;
64 while (triple) {
65 triple->dump();
66 triple = triple->fNext;
67 }
68}
69
70void SkStrokeContour::dump() const {
71#ifdef SK_DEBUG
72 SkDebugf("id=%d ", fDebugID);
73#endif
74 SkDebugf("head:\n");
75 fHead->dumpAll();
76 SkDebugf("head cap:\n");
77 fHeadCap->dumpAll();
78 SkDebugf("tail cap:\n");
79 fTailCap->dumpAll();
80}
81
82void SkStrokeContour::dumpAll() const {
83 const SkStrokeContour* contour = this;
84 while (contour) {
85 contour->dump();
86 contour = contour->fNext;
87 }
88}
89#endif
90
91SkScalar gCurveDistance = 10;
92
93#if 0 // unused
94static SkPath::Verb get_path_verb(int index, const SkPath& path) {
95 if (index < 0) {
96 return SkPath::kMove_Verb;
97 }
98 SkPoint pts[4];
99 SkPath::Verb verb;
100 SkPath::Iter iter(path, true);
101 int counter = -1;
102 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
103 if (++counter < index) {
104 continue;
105 }
106 return verb;
107 }
108 SkASSERT(0);
109 return SkPath::kMove_Verb;
110}
111#endif
112
113static SkScalar get_path_weight(int index, const SkPath& path) {
114 SkPoint pts[4];
115 SkPath::Verb verb;
116 SkPath::Iter iter(path, true);
117 int counter = -1;
118 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
119 if (++counter < index) {
120 continue;
121 }
122 return verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
123 }
124 SkASSERT(0);
125 return 0;
126}
127
128static void set_path_pt(int index, const SkPoint& pt, SkPath* path) {
129 SkPath result;
130 SkPoint pts[4];
131 SkPath::Verb verb;
132 SkPath::RawIter iter(*path);
133 int startIndex = 0;
134 int endIndex = 0;
135 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
136 switch (verb) {
137 case SkPath::kMove_Verb:
138 endIndex += 1;
139 break;
140 case SkPath::kLine_Verb:
141 endIndex += 1;
142 break;
143 case SkPath::kQuad_Verb:
144 case SkPath::kConic_Verb:
145 endIndex += 2;
146 break;
147 case SkPath::kCubic_Verb:
148 endIndex += 3;
149 break;
150 case SkPath::kClose_Verb:
151 break;
152 case SkPath::kDone_Verb:
153 break;
154 default:
155 SkASSERT(0);
156 }
157 if (startIndex <= index && index < endIndex) {
158 pts[index - startIndex] = pt;
159 index = -1;
160 }
161 switch (verb) {
162 case SkPath::kMove_Verb:
163 result.moveTo(pts[0]);
164 break;
165 case SkPath::kLine_Verb:
166 result.lineTo(pts[1]);
167 startIndex += 1;
168 break;
169 case SkPath::kQuad_Verb:
170 result.quadTo(pts[1], pts[2]);
171 startIndex += 2;
172 break;
173 case SkPath::kConic_Verb:
174 result.conicTo(pts[1], pts[2], iter.conicWeight());
175 startIndex += 2;
176 break;
177 case SkPath::kCubic_Verb:
178 result.cubicTo(pts[1], pts[2], pts[3]);
179 startIndex += 3;
180 break;
181 case SkPath::kClose_Verb:
182 result.close();
183 startIndex += 1;
184 break;
185 case SkPath::kDone_Verb:
186 break;
187 default:
188 SkASSERT(0);
189 }
190 }
191#if 0
192 SkDebugf("\n\noriginal\n");
193 path->dump();
194 SkDebugf("\nedited\n");
195 result.dump();
196#endif
197 *path = result;
198}
199
200static void add_path_segment(int index, SkPath* path) {
201 SkPath result;
202 SkPoint pts[4];
203 SkPoint firstPt = { 0, 0 }; // init to avoid warning
204 SkPoint lastPt = { 0, 0 }; // init to avoid warning
205 SkPath::Verb verb;
206 SkPath::RawIter iter(*path);
207 int counter = -1;
208 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
209 SkScalar weight SK_INIT_TO_AVOID_WARNING;
210 if (++counter == index) {
211 switch (verb) {
212 case SkPath::kLine_Verb:
213 result.lineTo((pts[0].fX + pts[1].fX) / 2, (pts[0].fY + pts[1].fY) / 2);
214 break;
215 case SkPath::kQuad_Verb: {
216 SkPoint chop[5];
217 SkChopQuadAtHalf(pts, chop);
218 result.quadTo(chop[1], chop[2]);
219 pts[1] = chop[3];
220 } break;
221 case SkPath::kConic_Verb: {
222 SkConic chop[2];
223 SkConic conic;
224 conic.set(pts, iter.conicWeight());
caryclark414c4292016-09-26 11:03:54 -0700225 if (!conic.chopAt(0.5f, chop)) {
226 return;
227 }
caryclark64022c12016-05-27 05:13:26 -0700228 result.conicTo(chop[0].fPts[1], chop[0].fPts[2], chop[0].fW);
229 pts[1] = chop[1].fPts[1];
230 weight = chop[1].fW;
231 } break;
232 case SkPath::kCubic_Verb: {
233 SkPoint chop[7];
234 SkChopCubicAtHalf(pts, chop);
235 result.cubicTo(chop[1], chop[2], chop[3]);
236 pts[1] = chop[4];
237 pts[2] = chop[5];
238 } break;
239 case SkPath::kClose_Verb: {
240 result.lineTo((lastPt.fX + firstPt.fX) / 2, (lastPt.fY + firstPt.fY) / 2);
241 } break;
242 default:
243 SkASSERT(0);
244 }
245 } else if (verb == SkPath::kConic_Verb) {
246 weight = iter.conicWeight();
247 }
248 switch (verb) {
249 case SkPath::kMove_Verb:
250 result.moveTo(firstPt = pts[0]);
251 break;
252 case SkPath::kLine_Verb:
253 result.lineTo(lastPt = pts[1]);
254 break;
255 case SkPath::kQuad_Verb:
256 result.quadTo(pts[1], lastPt = pts[2]);
257 break;
258 case SkPath::kConic_Verb:
259 result.conicTo(pts[1], lastPt = pts[2], weight);
260 break;
261 case SkPath::kCubic_Verb:
262 result.cubicTo(pts[1], pts[2], lastPt = pts[3]);
263 break;
264 case SkPath::kClose_Verb:
265 result.close();
266 break;
267 case SkPath::kDone_Verb:
268 break;
269 default:
270 SkASSERT(0);
271 }
272 }
273 *path = result;
274}
275
276static void delete_path_segment(int index, SkPath* path) {
277 SkPath result;
278 SkPoint pts[4];
279 SkPath::Verb verb;
280 SkPath::RawIter iter(*path);
281 int counter = -1;
282 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
283 if (++counter == index) {
284 continue;
285 }
286 switch (verb) {
287 case SkPath::kMove_Verb:
288 result.moveTo(pts[0]);
289 break;
290 case SkPath::kLine_Verb:
291 result.lineTo(pts[1]);
292 break;
293 case SkPath::kQuad_Verb:
294 result.quadTo(pts[1], pts[2]);
295 break;
296 case SkPath::kConic_Verb:
297 result.conicTo(pts[1], pts[2], iter.conicWeight());
298 break;
299 case SkPath::kCubic_Verb:
300 result.cubicTo(pts[1], pts[2], pts[3]);
301 break;
302 case SkPath::kClose_Verb:
303 result.close();
304 break;
305 case SkPath::kDone_Verb:
306 break;
307 default:
308 SkASSERT(0);
309 }
310 }
311 *path = result;
312}
313
314static void set_path_weight(int index, SkScalar w, SkPath* path) {
315 SkPath result;
316 SkPoint pts[4];
317 SkPath::Verb verb;
318 SkPath::Iter iter(*path, true);
319 int counter = -1;
320 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
321 ++counter;
322 switch (verb) {
323 case SkPath::kMove_Verb:
324 result.moveTo(pts[0]);
325 break;
326 case SkPath::kLine_Verb:
327 result.lineTo(pts[1]);
328 break;
329 case SkPath::kQuad_Verb:
330 result.quadTo(pts[1], pts[2]);
331 break;
332 case SkPath::kConic_Verb:
333 result.conicTo(pts[1], pts[2], counter == index ? w : iter.conicWeight());
334 break;
335 case SkPath::kCubic_Verb:
336 result.cubicTo(pts[1], pts[2], pts[3]);
337 break;
338 case SkPath::kClose_Verb:
339 result.close();
340 break;
341 case SkPath::kDone_Verb:
342 break;
343 default:
344 SkASSERT(0);
345 }
346 }
347 *path = result;
348}
349
350static void set_path_verb(int index, SkPath::Verb v, SkPath* path, SkScalar w) {
351 SkASSERT(SkPath::kLine_Verb <= v && v <= SkPath::kCubic_Verb);
352 SkPath result;
353 SkPoint pts[4];
354 SkPath::Verb verb;
355 SkPath::Iter iter(*path, true);
356 int counter = -1;
357 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
358 SkScalar weight = verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
359 if (++counter == index && v != verb) {
360 SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
361 switch (verb) {
362 case SkPath::kLine_Verb:
363 switch (v) {
364 case SkPath::kConic_Verb:
365 weight = w;
366 case SkPath::kQuad_Verb:
367 pts[2] = pts[1];
368 pts[1].fX = (pts[0].fX + pts[2].fX) / 2;
369 pts[1].fY = (pts[0].fY + pts[2].fY) / 2;
370 break;
371 case SkPath::kCubic_Verb:
372 pts[3] = pts[1];
373 pts[1].fX = (pts[0].fX * 2 + pts[3].fX) / 3;
374 pts[1].fY = (pts[0].fY * 2 + pts[3].fY) / 3;
375 pts[2].fX = (pts[0].fX + pts[3].fX * 2) / 3;
376 pts[2].fY = (pts[0].fY + pts[3].fY * 2) / 3;
377 break;
378 default:
379 SkASSERT(0);
380 break;
381 }
382 break;
383 case SkPath::kQuad_Verb:
384 case SkPath::kConic_Verb:
385 switch (v) {
386 case SkPath::kLine_Verb:
387 pts[1] = pts[2];
388 break;
389 case SkPath::kConic_Verb:
390 weight = w;
391 case SkPath::kQuad_Verb:
392 break;
393 case SkPath::kCubic_Verb: {
394 SkDQuad dQuad;
395 dQuad.set(pts);
396 SkDCubic dCubic = dQuad.debugToCubic();
397 pts[3] = pts[2];
398 pts[1] = dCubic[1].asSkPoint();
399 pts[2] = dCubic[2].asSkPoint();
400 } break;
401 default:
402 SkASSERT(0);
403 break;
404 }
405 break;
406 case SkPath::kCubic_Verb:
407 switch (v) {
408 case SkPath::kLine_Verb:
409 pts[1] = pts[3];
410 break;
411 case SkPath::kConic_Verb:
412 weight = w;
413 case SkPath::kQuad_Verb: {
414 SkDCubic dCubic;
415 dCubic.set(pts);
416 SkDQuad dQuad = dCubic.toQuad();
417 pts[1] = dQuad[1].asSkPoint();
418 pts[2] = pts[3];
419 } break;
420 default:
421 SkASSERT(0);
422 break;
423 }
424 break;
425 default:
426 SkASSERT(0);
427 break;
428 }
429 verb = v;
430 }
431 switch (verb) {
432 case SkPath::kMove_Verb:
433 result.moveTo(pts[0]);
434 break;
435 case SkPath::kLine_Verb:
436 result.lineTo(pts[1]);
437 break;
438 case SkPath::kQuad_Verb:
439 result.quadTo(pts[1], pts[2]);
440 break;
441 case SkPath::kConic_Verb:
442 result.conicTo(pts[1], pts[2], weight);
443 break;
444 case SkPath::kCubic_Verb:
445 result.cubicTo(pts[1], pts[2], pts[3]);
446 break;
447 case SkPath::kClose_Verb:
448 result.close();
449 break;
450 default:
451 SkASSERT(0);
452 break;
453 }
454 }
455 *path = result;
456}
457
458static void add_to_map(SkScalar coverage, int x, int y, uint8_t* distanceMap, int w, int h) {
459 int byteCoverage = (int) (coverage * 256);
460 if (byteCoverage < 0) {
461 byteCoverage = 0;
462 } else if (byteCoverage > 255) {
463 byteCoverage = 255;
464 }
465 SkASSERT(x < w);
466 SkASSERT(y < h);
467 distanceMap[y * w + x] = SkTMax(distanceMap[y * w + x], (uint8_t) byteCoverage);
468}
469
470static void filter_coverage(const uint8_t* map, int len, uint8_t min, uint8_t max,
471 uint8_t* filter) {
472 for (int index = 0; index < len; ++index) {
473 uint8_t in = map[index];
474 filter[index] = in < min ? 0 : max < in ? 0 : in;
475 }
476}
477
478static void construct_path(SkPath& path) {
479 path.reset();
480 path.moveTo(442, 101.5f);
481 path.quadTo(413.5f, 691, 772, 514);
482 path.lineTo(346, 721.5f);
483 path.lineTo(154, 209);
484 path.lineTo(442, 101.5f);
485 path.close();
486}
487
488struct ButtonPaints {
489 static const int kMaxStateCount = 3;
490 SkPaint fDisabled;
491 SkPaint fStates[kMaxStateCount];
492 SkPaint fLabel;
493
494 ButtonPaints() {
495 fStates[0].setAntiAlias(true);
496 fStates[0].setStyle(SkPaint::kStroke_Style);
497 fStates[0].setColor(0xFF3F0000);
498 fStates[1] = fStates[0];
499 fStates[1].setStrokeWidth(3);
500 fStates[2] = fStates[1];
501 fStates[2].setColor(0xFFcf0000);
502 fLabel.setAntiAlias(true);
503 fLabel.setTextSize(25.0f);
504 fLabel.setTextAlign(SkPaint::kCenter_Align);
505 fLabel.setStyle(SkPaint::kFill_Style);
506 }
507};
508
509struct Button {
510 SkRect fBounds;
511 int fStateCount;
512 int fState;
513 char fLabel;
514 bool fVisible;
515
516 Button(char label) {
517 fStateCount = 2;
518 fState = 0;
519 fLabel = label;
520 fVisible = false;
521 }
522
523 Button(char label, int stateCount) {
524 SkASSERT(stateCount <= ButtonPaints::kMaxStateCount);
525 fStateCount = stateCount;
526 fState = 0;
527 fLabel = label;
528 fVisible = false;
529 }
530
531 bool contains(const SkRect& rect) {
532 return fVisible && fBounds.contains(rect);
533 }
534
535 bool enabled() {
536 return SkToBool(fState);
537 }
538
539 void draw(SkCanvas* canvas, const ButtonPaints& paints) {
540 if (!fVisible) {
541 return;
542 }
543 canvas->drawRect(fBounds, paints.fStates[fState]);
544 canvas->drawText(&fLabel, 1, fBounds.centerX(), fBounds.fBottom - 5, paints.fLabel);
545 }
546
547 void toggle() {
548 if (++fState == fStateCount) {
549 fState = 0;
550 }
551 }
552
553 void setEnabled(bool enabled) {
554 fState = (int) enabled;
555 }
556};
557
558struct ControlPaints {
559 SkPaint fOutline;
560 SkPaint fIndicator;
561 SkPaint fFill;
562 SkPaint fLabel;
563 SkPaint fValue;
564
565 ControlPaints() {
566 fOutline.setAntiAlias(true);
567 fOutline.setStyle(SkPaint::kStroke_Style);
568 fIndicator = fOutline;
569 fIndicator.setColor(SK_ColorRED);
570 fFill.setAntiAlias(true);
571 fFill.setColor(0x7fff0000);
572 fLabel.setAntiAlias(true);
573 fLabel.setTextSize(13.0f);
574 fValue.setAntiAlias(true);
575 fValue.setTextSize(11.0f);
576 }
577};
578
579struct UniControl {
580 SkString fName;
581 SkRect fBounds;
582 SkScalar fMin;
583 SkScalar fMax;
584 SkScalar fValLo;
585 SkScalar fYLo;
586 bool fVisible;
587
588 UniControl(const char* name, SkScalar min, SkScalar max) {
589 fName = name;
590 fValLo = fMin = min;
591 fMax = max;
592 fVisible = false;
593
594 }
595
596 virtual ~UniControl() {}
597
598 bool contains(const SkRect& rect) {
599 return fVisible && fBounds.contains(rect);
600 }
601
602 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
603 if (!fVisible) {
604 return;
605 }
606 canvas->drawRect(fBounds, paints.fOutline);
607 fYLo = fBounds.fTop + (fValLo - fMin) * fBounds.height() / (fMax - fMin);
608 canvas->drawLine(fBounds.fLeft - 5, fYLo, fBounds.fRight + 5, fYLo, paints.fIndicator);
609 SkString label;
610 label.printf("%0.3g", fValLo);
Cary Clark2a475ea2017-04-28 15:35:12 -0400611 canvas->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValue);
612 canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11,
caryclark64022c12016-05-27 05:13:26 -0700613 paints.fLabel);
614 }
615};
616
617struct BiControl : public UniControl {
618 SkScalar fValHi;
619
Ben Wagner63fd7602017-10-09 15:45:33 -0400620 BiControl(const char* name, SkScalar min, SkScalar max)
caryclark64022c12016-05-27 05:13:26 -0700621 : UniControl(name, min, max)
622 , fValHi(fMax) {
623 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400624
caryclark64022c12016-05-27 05:13:26 -0700625 virtual ~BiControl() {}
626
627 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
628 UniControl::draw(canvas, paints);
629 if (!fVisible || fValHi == fValLo) {
630 return;
631 }
632 SkScalar yPos = fBounds.fTop + (fValHi - fMin) * fBounds.height() / (fMax - fMin);
633 canvas->drawLine(fBounds.fLeft - 5, yPos, fBounds.fRight + 5, yPos, paints.fIndicator);
634 SkString label;
635 label.printf("%0.3g", fValHi);
636 if (yPos < fYLo + 10) {
637 yPos = fYLo + 10;
638 }
Cary Clark2a475ea2017-04-28 15:35:12 -0400639 canvas->drawString(label, fBounds.fLeft + 5, yPos - 5, paints.fValue);
caryclark64022c12016-05-27 05:13:26 -0700640 SkRect fill = { fBounds.fLeft, fYLo, fBounds.fRight, yPos };
641 canvas->drawRect(fill, paints.fFill);
642 }
643};
644
645
646class MyClick : public SampleView::Click {
647public:
648 enum ClickType {
649 kInvalidType = -1,
650 kPtType,
651 kVerbType,
652 kControlType,
653 kPathType,
654 } fType;
655
656 enum ControlType {
657 kInvalidControl = -1,
658 kFirstControl,
659 kFilterControl = kFirstControl,
660 kResControl,
661 kWeightControl,
662 kWidthControl,
663 kLastControl = kWidthControl,
664 kFirstButton,
665 kCubicButton = kFirstButton,
666 kConicButton,
667 kQuadButton,
668 kLineButton,
669 kLastVerbButton = kLineButton,
670 kAddButton,
671 kDeleteButton,
672 kInOutButton,
673 kFillButton,
674 kSkeletonButton,
675 kFilterButton,
676 kBisectButton,
677 kJoinButton,
678 kLastButton = kJoinButton,
679 kPathMove,
680 } fControl;
681
682 SkPath::Verb fVerb;
683 SkScalar fWeight;
684
685 MyClick(SkView* target, ClickType type, ControlType control)
686 : Click(target)
Ben Wagner63fd7602017-10-09 15:45:33 -0400687 , fType(type)
caryclark64022c12016-05-27 05:13:26 -0700688 , fControl(control)
689 , fVerb((SkPath::Verb) -1)
690 , fWeight(1) {
691 }
692
693 MyClick(SkView* target, ClickType type, int index)
694 : Click(target)
Ben Wagner63fd7602017-10-09 15:45:33 -0400695 , fType(type)
caryclark64022c12016-05-27 05:13:26 -0700696 , fControl((ControlType) index)
697 , fVerb((SkPath::Verb) -1)
698 , fWeight(1) {
699 }
700
701 MyClick(SkView* target, ClickType type, int index, SkPath::Verb verb, SkScalar weight)
702 : Click(target)
Ben Wagner63fd7602017-10-09 15:45:33 -0400703 , fType(type)
caryclark64022c12016-05-27 05:13:26 -0700704 , fControl((ControlType) index)
705 , fVerb(verb)
706 , fWeight(weight) {
707 }
708
709 bool isButton() {
710 return kFirstButton <= fControl && fControl <= kLastButton;
711 }
712
713 int ptHit() const {
714 SkASSERT(fType == kPtType);
715 return (int) fControl;
716 }
717
718 int verbHit() const {
719 SkASSERT(fType == kVerbType);
720 return (int) fControl;
721 }
722};
723
724enum {
725 kControlCount = MyClick::kLastControl - MyClick::kFirstControl + 1,
726};
727
728static struct ControlPair {
729 UniControl* fControl;
730 MyClick::ControlType fControlType;
731} kControlList[kControlCount];
732
733enum {
734 kButtonCount = MyClick::kLastButton - MyClick::kFirstButton + 1,
735 kVerbCount = MyClick::kLastVerbButton - MyClick::kFirstButton + 1,
736};
737
738static struct ButtonPair {
739 Button* fButton;
740 MyClick::ControlType fButtonType;
741} kButtonList[kButtonCount];
742
743static void enable_verb_button(MyClick::ControlType type) {
744 for (int index = 0; index < kButtonCount; ++index) {
745 MyClick::ControlType testType = kButtonList[index].fButtonType;
746 if (MyClick::kFirstButton <= testType && testType <= MyClick::kLastVerbButton) {
747 Button* button = kButtonList[index].fButton;
748 button->setEnabled(testType == type);
749 }
750 }
751}
752
753struct Stroke;
754
755struct Active {
756 Active* fNext;
757 Stroke* fParent;
758 SkScalar fStart;
759 SkScalar fEnd;
760
761 void reset() {
Ben Wagnera93a14a2017-08-28 10:34:05 -0400762 fNext = nullptr;
caryclark64022c12016-05-27 05:13:26 -0700763 fStart = 0;
764 fEnd = 1;
765 }
766};
767
768struct Stroke {
769 SkPath fPath;
770 Active fActive;
771 bool fInner;
772
773 void reset() {
774 fPath.reset();
775 fActive.reset();
776 }
777};
778
779struct PathUndo {
780 SkPath fPath;
781 PathUndo* fNext;
782};
783
784class AAGeometryView : public SampleView {
785 SkPaint fActivePaint;
786 SkPaint fComplexPaint;
787 SkPaint fCoveragePaint;
788 SkPaint fLegendLeftPaint;
789 SkPaint fLegendRightPaint;
790 SkPaint fPointPaint;
791 SkPaint fSkeletonPaint;
792 SkPaint fLightSkeletonPaint;
793 SkPath fPath;
794 ControlPaints fControlPaints;
795 UniControl fResControl;
796 UniControl fWeightControl;
797 UniControl fWidthControl;
798 BiControl fFilterControl;
799 ButtonPaints fButtonPaints;
800 Button fCubicButton;
801 Button fConicButton;
802 Button fQuadButton;
803 Button fLineButton;
804 Button fAddButton;
805 Button fDeleteButton;
806 Button fFillButton;
807 Button fSkeletonButton;
808 Button fFilterButton;
809 Button fBisectButton;
810 Button fJoinButton;
811 Button fInOutButton;
812 SkTArray<Stroke> fStrokes;
813 PathUndo* fUndo;
814 int fActivePt;
815 int fActiveVerb;
816 bool fHandlePathMove;
817 bool fShowLegend;
818 bool fHideAll;
Chris Dalton08d1a252017-10-20 11:46:47 -0600819 const int kHitToleranace = 25;
caryclark64022c12016-05-27 05:13:26 -0700820
821public:
822
Ben Wagner63fd7602017-10-09 15:45:33 -0400823 AAGeometryView()
caryclark64022c12016-05-27 05:13:26 -0700824 : fResControl("error", 0, 10)
825 , fWeightControl("weight", 0, 5)
826 , fWidthControl("width", FLT_EPSILON, 100)
827 , fFilterControl("filter", 0, 255)
828 , fCubicButton('C')
829 , fConicButton('K')
830 , fQuadButton('Q')
831 , fLineButton('L')
832 , fAddButton('+')
833 , fDeleteButton('x')
834 , fFillButton('p')
835 , fSkeletonButton('s')
836 , fFilterButton('f', 3)
837 , fBisectButton('b')
838 , fJoinButton('j')
839 , fInOutButton('|')
Ben Wagnera93a14a2017-08-28 10:34:05 -0400840 , fUndo(nullptr)
caryclark64022c12016-05-27 05:13:26 -0700841 , fActivePt(-1)
842 , fActiveVerb(-1)
843 , fHandlePathMove(true)
844 , fShowLegend(false)
845 , fHideAll(false)
846 {
847 fCoveragePaint.setAntiAlias(true);
848 fCoveragePaint.setColor(SK_ColorBLUE);
849 SkPaint strokePaint;
850 strokePaint.setAntiAlias(true);
851 strokePaint.setStyle(SkPaint::kStroke_Style);
852 fPointPaint = strokePaint;
853 fPointPaint.setColor(0x99ee3300);
854 fSkeletonPaint = strokePaint;
855 fSkeletonPaint.setColor(SK_ColorRED);
856 fLightSkeletonPaint = fSkeletonPaint;
857 fLightSkeletonPaint.setColor(0xFFFF7f7f);
858 fActivePaint = strokePaint;
859 fActivePaint.setColor(0x99ee3300);
860 fActivePaint.setStrokeWidth(5);
861 fComplexPaint = fActivePaint;
862 fComplexPaint.setColor(SK_ColorBLUE);
863 fLegendLeftPaint.setAntiAlias(true);
864 fLegendLeftPaint.setTextSize(13);
865 fLegendRightPaint = fLegendLeftPaint;
866 fLegendRightPaint.setTextAlign(SkPaint::kRight_Align);
867 construct_path(fPath);
868 fFillButton.fVisible = fSkeletonButton.fVisible = fFilterButton.fVisible
869 = fBisectButton.fVisible = fJoinButton.fVisible = fInOutButton.fVisible = true;
870 fSkeletonButton.setEnabled(true);
871 fInOutButton.setEnabled(true);
872 fJoinButton.setEnabled(true);
873 fFilterControl.fValLo = 120;
874 fFilterControl.fValHi = 141;
875 fFilterControl.fVisible = fFilterButton.fState == 2;
876 fResControl.fValLo = 5;
877 fResControl.fVisible = true;
878 fWidthControl.fValLo = 50;
879 fWidthControl.fVisible = true;
880 init_controlList();
881 init_buttonList();
882 }
883
884 bool constructPath() {
885 construct_path(fPath);
Ben Wagnera93a14a2017-08-28 10:34:05 -0400886 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -0700887 return true;
888 }
889
890 void savePath(Click::State state) {
891 if (state != Click::kDown_State) {
892 return;
893 }
894 if (fUndo && fUndo->fPath == fPath) {
895 return;
896 }
897 PathUndo* undo = new PathUndo;
898 undo->fPath = fPath;
899 undo->fNext = fUndo;
900 fUndo = undo;
901 }
902
903 bool undo() {
904 if (!fUndo) {
905 return false;
906 }
907 fPath = fUndo->fPath;
908 validatePath();
909 PathUndo* next = fUndo->fNext;
910 delete fUndo;
911 fUndo = next;
Ben Wagnera93a14a2017-08-28 10:34:05 -0400912 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -0700913 return true;
914 }
915
916 void validatePath() {
917 PathUndo* undo = fUndo;
918 int match = 0;
919 while (undo) {
920 match += fPath == undo->fPath;
921 undo = undo->fNext;
922 }
923 }
924
925 void set_controlList(int index, UniControl* control, MyClick::ControlType type) {
926 kControlList[index].fControl = control;
927 kControlList[index].fControlType = type;
928 }
929
930 #define SET_CONTROL(Name) set_controlList(index++, &f##Name##Control, \
931 MyClick::k##Name##Control)
932
933 bool hideAll() {
934 fHideAll ^= true;
Ben Wagnera93a14a2017-08-28 10:34:05 -0400935 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -0700936 return true;
937 }
938
939 void init_controlList() {
940 int index = 0;
941 SET_CONTROL(Width);
942 SET_CONTROL(Res);
943 SET_CONTROL(Filter);
944 SET_CONTROL(Weight);
Brian Osman16adfa32016-10-18 14:42:44 -0400945 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400946
caryclark64022c12016-05-27 05:13:26 -0700947 #undef SET_CONTROL
948
949 void set_buttonList(int index, Button* button, MyClick::ControlType type) {
950 kButtonList[index].fButton = button;
951 kButtonList[index].fButtonType = type;
952 }
953
954 #define SET_BUTTON(Name) set_buttonList(index++, &f##Name##Button, \
955 MyClick::k##Name##Button)
956
957 void init_buttonList() {
958 int index = 0;
959 SET_BUTTON(Fill);
960 SET_BUTTON(Skeleton);
961 SET_BUTTON(Filter);
962 SET_BUTTON(Bisect);
963 SET_BUTTON(Join);
964 SET_BUTTON(InOut);
965 SET_BUTTON(Cubic);
966 SET_BUTTON(Conic);
967 SET_BUTTON(Quad);
968 SET_BUTTON(Line);
969 SET_BUTTON(Add);
970 SET_BUTTON(Delete);
971 }
972
973 #undef SET_BUTTON
974
975 // overrides from SkEventSink
976 bool onQuery(SkEvent* evt) override;
Ben Wagner63fd7602017-10-09 15:45:33 -0400977
caryclark64022c12016-05-27 05:13:26 -0700978 void onSizeChange() override {
979 setControlButtonsPos();
980 this->INHERITED::onSizeChange();
981 }
982
983 bool pathDump() {
984 fPath.dump();
985 return true;
986 }
987
988 bool scaleDown() {
989 SkMatrix matrix;
990 SkRect bounds = fPath.getBounds();
991 matrix.setScale(1.f / 1.5f, 1.f / 1.5f, bounds.centerX(), bounds.centerY());
992 fPath.transform(matrix);
993 validatePath();
Ben Wagnera93a14a2017-08-28 10:34:05 -0400994 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -0700995 return true;
996 }
997
998 bool scaleToFit() {
999 SkMatrix matrix;
1000 SkRect bounds = fPath.getBounds();
1001 SkScalar scale = SkTMin(this->width() / bounds.width(), this->height() / bounds.height())
1002 * 0.8f;
1003 matrix.setScale(scale, scale, bounds.centerX(), bounds.centerY());
1004 fPath.transform(matrix);
1005 bounds = fPath.getBounds();
1006 SkScalar offsetX = (this->width() - bounds.width()) / 2 - bounds.fLeft;
1007 SkScalar offsetY = (this->height() - bounds.height()) / 2 - bounds.fTop;
1008 fPath.offset(offsetX, offsetY);
1009 validatePath();
Ben Wagnera93a14a2017-08-28 10:34:05 -04001010 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -07001011 return true;
1012 }
1013
1014 bool scaleUp() {
1015 SkMatrix matrix;
1016 SkRect bounds = fPath.getBounds();
1017 matrix.setScale(1.5f, 1.5f, bounds.centerX(), bounds.centerY());
1018 fPath.transform(matrix);
1019 validatePath();
Ben Wagnera93a14a2017-08-28 10:34:05 -04001020 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -07001021 return true;
1022 }
1023
1024 void setControlButtonsPos() {
1025 SkScalar widthOffset = this->width() - 100;
1026 for (int index = 0; index < kControlCount; ++index) {
1027 if (kControlList[index].fControl->fVisible) {
1028 kControlList[index].fControl->fBounds.setXYWH(widthOffset, 30, 30, 400);
1029 widthOffset -= 50;
1030 }
1031 }
1032 SkScalar buttonOffset = 0;
1033 for (int index = 0; index < kButtonCount; ++index) {
1034 kButtonList[index].fButton->fBounds.setXYWH(this->width() - 50,
1035 buttonOffset += 50, 30, 30);
1036 }
1037 }
1038
1039 bool showLegend() {
1040 fShowLegend ^= true;
Ben Wagnera93a14a2017-08-28 10:34:05 -04001041 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -07001042 return true;
1043 }
1044
1045 void draw_bisect(SkCanvas* canvas, const SkVector& lastVector, const SkVector& vector,
1046 const SkPoint& pt) {
1047 SkVector lastV = lastVector;
1048 SkScalar lastLen = lastVector.length();
1049 SkVector nextV = vector;
1050 SkScalar nextLen = vector.length();
1051 if (lastLen < nextLen) {
1052 lastV.setLength(nextLen);
1053 } else {
1054 nextV.setLength(lastLen);
1055 }
1056
1057 SkVector bisect = { (lastV.fX + nextV.fX) / 2, (lastV.fY + nextV.fY) / 2 };
1058 bisect.setLength(fWidthControl.fValLo * 2);
1059 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -04001060 canvas->drawLine(pt, pt + bisect, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001061 }
1062 lastV.setLength(fWidthControl.fValLo);
1063 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -04001064 canvas->drawLine(pt, {pt.fX - lastV.fY, pt.fY + lastV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001065 }
1066 nextV.setLength(fWidthControl.fValLo);
1067 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -04001068 canvas->drawLine(pt, {pt.fX + nextV.fY, pt.fY - nextV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001069 }
1070 if (fJoinButton.enabled()) {
1071 SkScalar r = fWidthControl.fValLo;
1072 SkRect oval = { pt.fX - r, pt.fY - r, pt.fX + r, pt.fY + r};
1073 SkScalar startAngle = SkScalarATan2(lastV.fX, -lastV.fY) * 180.f / SK_ScalarPI;
1074 SkScalar endAngle = SkScalarATan2(-nextV.fX, nextV.fY) * 180.f / SK_ScalarPI;
1075 if (endAngle > startAngle) {
1076 canvas->drawArc(oval, startAngle, endAngle - startAngle, false, fSkeletonPaint);
1077 } else {
1078 canvas->drawArc(oval, startAngle, 360 - (startAngle - endAngle), false,
1079 fSkeletonPaint);
1080 }
1081 }
1082 }
1083
1084 void draw_bisects(SkCanvas* canvas, bool activeOnly) {
1085 SkVector firstVector, lastVector, nextLast, vector;
1086 SkPoint pts[4];
1087 SkPoint firstPt = { 0, 0 }; // init to avoid warning;
1088 SkPath::Verb verb;
1089 SkPath::Iter iter(fPath, true);
1090 bool foundFirst = false;
1091 int counter = -1;
1092 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1093 ++counter;
1094 if (activeOnly && counter != fActiveVerb && counter - 1 != fActiveVerb
1095 && counter + 1 != fActiveVerb
1096 && (fActiveVerb != 1 || counter != fPath.countVerbs())) {
1097 continue;
1098 }
1099 switch (verb) {
1100 case SkPath::kLine_Verb:
1101 nextLast = pts[0] - pts[1];
1102 vector = pts[1] - pts[0];
1103 break;
1104 case SkPath::kQuad_Verb: {
1105 nextLast = pts[1] - pts[2];
1106 if (SkScalarNearlyZero(nextLast.length())) {
1107 nextLast = pts[0] - pts[2];
1108 }
1109 vector = pts[1] - pts[0];
1110 if (SkScalarNearlyZero(vector.length())) {
1111 vector = pts[2] - pts[0];
1112 }
1113 if (!fBisectButton.enabled()) {
1114 break;
1115 }
1116 SkScalar t = SkFindQuadMaxCurvature(pts);
1117 if (0 < t && t < 1) {
1118 SkPoint maxPt = SkEvalQuadAt(pts, t);
1119 SkVector tangent = SkEvalQuadTangentAt(pts, t);
1120 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001121 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1122 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001123 }
1124 } break;
1125 case SkPath::kConic_Verb:
1126 nextLast = pts[1] - pts[2];
1127 if (SkScalarNearlyZero(nextLast.length())) {
1128 nextLast = pts[0] - pts[2];
1129 }
1130 vector = pts[1] - pts[0];
1131 if (SkScalarNearlyZero(vector.length())) {
1132 vector = pts[2] - pts[0];
1133 }
1134 if (!fBisectButton.enabled()) {
1135 break;
1136 }
1137 // FIXME : need max curvature or equivalent here
1138 break;
1139 case SkPath::kCubic_Verb: {
1140 nextLast = pts[2] - pts[3];
1141 if (SkScalarNearlyZero(nextLast.length())) {
1142 nextLast = pts[1] - pts[3];
1143 if (SkScalarNearlyZero(nextLast.length())) {
1144 nextLast = pts[0] - pts[3];
1145 }
1146 }
1147 vector = pts[0] - pts[1];
1148 if (SkScalarNearlyZero(vector.length())) {
1149 vector = pts[0] - pts[2];
1150 if (SkScalarNearlyZero(vector.length())) {
1151 vector = pts[0] - pts[3];
1152 }
1153 }
1154 if (!fBisectButton.enabled()) {
1155 break;
1156 }
1157 SkScalar tMax[2];
1158 int tMaxCount = SkFindCubicMaxCurvature(pts, tMax);
1159 for (int tIndex = 0; tIndex < tMaxCount; ++tIndex) {
1160 if (0 >= tMax[tIndex] || tMax[tIndex] >= 1) {
1161 continue;
1162 }
1163 SkPoint maxPt;
1164 SkVector tangent;
Ben Wagnera93a14a2017-08-28 10:34:05 -04001165 SkEvalCubicAt(pts, tMax[tIndex], &maxPt, &tangent, nullptr);
caryclark64022c12016-05-27 05:13:26 -07001166 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001167 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1168 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001169 }
1170 } break;
1171 case SkPath::kClose_Verb:
1172 if (foundFirst) {
1173 draw_bisect(canvas, lastVector, firstVector, firstPt);
1174 foundFirst = false;
1175 }
1176 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001177 default:
caryclark64022c12016-05-27 05:13:26 -07001178 break;
1179 }
1180 if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) {
1181 if (!foundFirst) {
1182 firstPt = pts[0];
1183 firstVector = vector;
1184 foundFirst = true;
1185 } else {
1186 draw_bisect(canvas, lastVector, vector, pts[0]);
1187 }
1188 lastVector = nextLast;
1189 }
1190 }
1191 }
1192
1193 void draw_legend(SkCanvas* canvas);
1194
1195 void draw_segment(SkCanvas* canvas) {
1196 SkPoint pts[4];
1197 SkPath::Verb verb;
1198 SkPath::Iter iter(fPath, true);
1199 int counter = -1;
1200 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1201 if (++counter < fActiveVerb) {
1202 continue;
1203 }
1204 switch (verb) {
1205 case SkPath::kLine_Verb:
1206 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, fActivePaint);
1207 draw_points(canvas, pts, 2);
1208 break;
1209 case SkPath::kQuad_Verb: {
1210 SkPath qPath;
1211 qPath.moveTo(pts[0]);
1212 qPath.quadTo(pts[1], pts[2]);
1213 canvas->drawPath(qPath, fActivePaint);
1214 draw_points(canvas, pts, 3);
1215 } break;
1216 case SkPath::kConic_Verb: {
1217 SkPath conicPath;
1218 conicPath.moveTo(pts[0]);
1219 conicPath.conicTo(pts[1], pts[2], iter.conicWeight());
1220 canvas->drawPath(conicPath, fActivePaint);
1221 draw_points(canvas, pts, 3);
1222 } break;
1223 case SkPath::kCubic_Verb: {
Cary Clark7eb01e02016-12-08 14:36:32 -05001224 SkScalar loopT[3];
1225 int complex = SkDCubic::ComplexBreak(pts, loopT);
caryclark64022c12016-05-27 05:13:26 -07001226 SkPath cPath;
1227 cPath.moveTo(pts[0]);
1228 cPath.cubicTo(pts[1], pts[2], pts[3]);
1229 canvas->drawPath(cPath, complex ? fComplexPaint : fActivePaint);
1230 draw_points(canvas, pts, 4);
1231 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001232 default:
caryclark64022c12016-05-27 05:13:26 -07001233 break;
1234 }
1235 return;
1236 }
1237 }
1238
1239 void draw_points(SkCanvas* canvas, SkPoint* points, int count) {
1240 for (int index = 0; index < count; ++index) {
1241 canvas->drawCircle(points[index].fX, points[index].fY, 10, fPointPaint);
1242 }
1243 }
1244
1245 int hittest_verb(SkPoint pt, SkPath::Verb* verbPtr, SkScalar* weight) {
1246 SkIntersections i;
1247 SkDLine hHit = {{{pt.fX - kHitToleranace, pt.fY }, {pt.fX + kHitToleranace, pt.fY}}};
1248 SkDLine vHit = {{{pt.fX, pt.fY - kHitToleranace }, {pt.fX, pt.fY + kHitToleranace}}};
1249 SkPoint pts[4];
1250 SkPath::Verb verb;
1251 SkPath::Iter iter(fPath, true);
1252 int counter = -1;
1253 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1254 ++counter;
1255 switch (verb) {
1256 case SkPath::kLine_Verb: {
1257 SkDLine line;
1258 line.set(pts);
1259 if (i.intersect(line, hHit) || i.intersect(line, vHit)) {
1260 *verbPtr = verb;
1261 *weight = 1;
1262 return counter;
1263 }
1264 } break;
1265 case SkPath::kQuad_Verb: {
1266 SkDQuad quad;
1267 quad.set(pts);
1268 if (i.intersect(quad, hHit) || i.intersect(quad, vHit)) {
1269 *verbPtr = verb;
1270 *weight = 1;
1271 return counter;
1272 }
1273 } break;
1274 case SkPath::kConic_Verb: {
1275 SkDConic conic;
1276 SkScalar w = iter.conicWeight();
1277 conic.set(pts, w);
1278 if (i.intersect(conic, hHit) || i.intersect(conic, vHit)) {
1279 *verbPtr = verb;
1280 *weight = w;
1281 return counter;
1282 }
1283 } break;
1284 case SkPath::kCubic_Verb: {
1285 SkDCubic cubic;
1286 cubic.set(pts);
1287 if (i.intersect(cubic, hHit) || i.intersect(cubic, vHit)) {
1288 *verbPtr = verb;
1289 *weight = 1;
1290 return counter;
1291 }
1292 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001293 default:
caryclark64022c12016-05-27 05:13:26 -07001294 break;
1295 }
1296 }
1297 return -1;
1298 }
1299
1300 SkScalar pt_to_line(SkPoint s, SkPoint e, int x, int y) {
1301 SkScalar radius = fWidthControl.fValLo;
1302 SkVector adjOpp = e - s;
Cary Clarkdf429f32017-11-08 11:44:31 -05001303 SkScalar lenSq = SkPointPriv::LengthSqd(adjOpp);
caryclark64022c12016-05-27 05:13:26 -07001304 SkPoint rotated = {
1305 (y - s.fY) * adjOpp.fY + (x - s.fX) * adjOpp.fX,
1306 (y - s.fY) * adjOpp.fX - (x - s.fX) * adjOpp.fY,
1307 };
1308 if (rotated.fX < 0 || rotated.fX > lenSq) {
1309 return -radius;
1310 }
1311 rotated.fY /= SkScalarSqrt(lenSq);
1312 return SkTMax(-radius, SkTMin(radius, rotated.fY));
1313 }
1314
1315 // given a line, compute the interior and exterior gradient coverage
1316 bool coverage(SkPoint s, SkPoint e, uint8_t* distanceMap, int w, int h) {
1317 SkScalar radius = fWidthControl.fValLo;
1318 int minX = SkTMax(0, (int) (SkTMin(s.fX, e.fX) - radius));
1319 int minY = SkTMax(0, (int) (SkTMin(s.fY, e.fY) - radius));
1320 int maxX = SkTMin(w, (int) (SkTMax(s.fX, e.fX) + radius) + 1);
1321 int maxY = SkTMin(h, (int) (SkTMax(s.fY, e.fY) + radius) + 1);
1322 for (int y = minY; y < maxY; ++y) {
1323 for (int x = minX; x < maxX; ++x) {
1324 SkScalar ptToLineDist = pt_to_line(s, e, x, y);
1325 if (ptToLineDist > -radius && ptToLineDist < radius) {
1326 SkScalar coverage = ptToLineDist / radius;
1327 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1328 }
1329 SkVector ptToS = { x - s.fX, y - s.fY };
1330 SkScalar dist = ptToS.length();
1331 if (dist < radius) {
1332 SkScalar coverage = dist / radius;
1333 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1334 }
1335 SkVector ptToE = { x - e.fX, y - e.fY };
1336 dist = ptToE.length();
1337 if (dist < radius) {
1338 SkScalar coverage = dist / radius;
1339 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1340 }
1341 }
1342 }
1343 return true;
1344 }
1345
1346 void quad_coverage(SkPoint pts[3], uint8_t* distanceMap, int w, int h) {
1347 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1348 if (dist < gCurveDistance) {
1349 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1350 return;
1351 }
1352 SkPoint split[5];
1353 SkChopQuadAt(pts, split, 0.5f);
1354 quad_coverage(&split[0], distanceMap, w, h);
1355 quad_coverage(&split[2], distanceMap, w, h);
1356 }
1357
1358 void conic_coverage(SkPoint pts[3], SkScalar weight, uint8_t* distanceMap, int w, int h) {
1359 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1360 if (dist < gCurveDistance) {
1361 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1362 return;
1363 }
1364 SkConic split[2];
1365 SkConic conic;
1366 conic.set(pts, weight);
caryclark414c4292016-09-26 11:03:54 -07001367 if (conic.chopAt(0.5f, split)) {
1368 conic_coverage(split[0].fPts, split[0].fW, distanceMap, w, h);
1369 conic_coverage(split[1].fPts, split[1].fW, distanceMap, w, h);
1370 }
caryclark64022c12016-05-27 05:13:26 -07001371 }
1372
1373 void cubic_coverage(SkPoint pts[4], uint8_t* distanceMap, int w, int h) {
1374 SkScalar dist = pts[0].Distance(pts[0], pts[3]);
1375 if (dist < gCurveDistance) {
1376 (void) coverage(pts[0], pts[3], distanceMap, w, h);
1377 return;
1378 }
1379 SkPoint split[7];
1380 SkChopCubicAt(pts, split, 0.5f);
1381 cubic_coverage(&split[0], distanceMap, w, h);
1382 cubic_coverage(&split[3], distanceMap, w, h);
1383 }
1384
1385 void path_coverage(const SkPath& path, uint8_t* distanceMap, int w, int h) {
1386 memset(distanceMap, 0, sizeof(distanceMap[0]) * w * h);
1387 SkPoint pts[4];
1388 SkPath::Verb verb;
1389 SkPath::Iter iter(path, true);
1390 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1391 switch (verb) {
1392 case SkPath::kLine_Verb:
1393 (void) coverage(pts[0], pts[1], distanceMap, w, h);
1394 break;
1395 case SkPath::kQuad_Verb:
1396 quad_coverage(pts, distanceMap, w, h);
1397 break;
1398 case SkPath::kConic_Verb:
1399 conic_coverage(pts, iter.conicWeight(), distanceMap, w, h);
1400 break;
1401 case SkPath::kCubic_Verb:
1402 cubic_coverage(pts, distanceMap, w, h);
1403 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001404 default:
caryclark64022c12016-05-27 05:13:26 -07001405 break;
1406 }
1407 }
1408 }
1409
1410 static uint8_t* set_up_dist_map(const SkImageInfo& imageInfo, SkBitmap* distMap) {
1411 distMap->setInfo(imageInfo);
1412 distMap->setIsVolatile(true);
1413 SkAssertResult(distMap->tryAllocPixels());
1414 SkASSERT((int) distMap->rowBytes() == imageInfo.width());
1415 return distMap->getAddr8(0, 0);
1416 }
1417
1418 void path_stroke(int index, SkPath* inner, SkPath* outer) {
1419 #if 0
1420 SkPathStroker stroker(fPath, fWidthControl.fValLo, 0,
1421 SkPaint::kRound_Cap, SkPaint::kRound_Join, fResControl.fValLo);
1422 SkPoint pts[4], firstPt, lastPt;
1423 SkPath::Verb verb;
1424 SkPath::Iter iter(fPath, true);
1425 int counter = -1;
1426 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1427 ++counter;
1428 switch (verb) {
1429 case SkPath::kMove_Verb:
1430 firstPt = pts[0];
1431 break;
1432 case SkPath::kLine_Verb:
1433 if (counter == index) {
1434 stroker.moveTo(pts[0]);
1435 stroker.lineTo(pts[1]);
1436 goto done;
1437 }
1438 lastPt = pts[1];
1439 break;
1440 case SkPath::kQuad_Verb:
1441 if (counter == index) {
1442 stroker.moveTo(pts[0]);
1443 stroker.quadTo(pts[1], pts[2]);
1444 goto done;
1445 }
1446 lastPt = pts[2];
1447 break;
1448 case SkPath::kConic_Verb:
1449 if (counter == index) {
1450 stroker.moveTo(pts[0]);
1451 stroker.conicTo(pts[1], pts[2], iter.conicWeight());
1452 goto done;
1453 }
1454 lastPt = pts[2];
1455 break;
1456 case SkPath::kCubic_Verb:
1457 if (counter == index) {
1458 stroker.moveTo(pts[0]);
1459 stroker.cubicTo(pts[1], pts[2], pts[3]);
1460 goto done;
1461 }
1462 lastPt = pts[3];
1463 break;
1464 case SkPath::kClose_Verb:
1465 if (counter == index) {
1466 stroker.moveTo(lastPt);
1467 stroker.lineTo(firstPt);
1468 goto done;
1469 }
1470 break;
1471 case SkPath::kDone_Verb:
1472 break;
1473 default:
1474 SkASSERT(0);
1475 }
1476 }
1477 done:
1478 *inner = stroker.fInner;
1479 *outer = stroker.fOuter;
1480#endif
1481 }
1482
1483 void draw_stroke(SkCanvas* canvas, int active) {
1484 SkPath inner, outer;
1485 path_stroke(active, &inner, &outer);
1486 canvas->drawPath(inner, fSkeletonPaint);
1487 canvas->drawPath(outer, fSkeletonPaint);
1488 }
1489
1490 void gather_strokes() {
1491 fStrokes.reset();
1492 for (int index = 0; index < fPath.countVerbs(); ++index) {
1493 Stroke& inner = fStrokes.push_back();
1494 inner.reset();
1495 inner.fInner = true;
1496 Stroke& outer = fStrokes.push_back();
1497 outer.reset();
1498 outer.fInner = false;
1499 path_stroke(index, &inner.fPath, &outer.fPath);
1500 }
1501 }
1502
1503 void trim_strokes() {
1504 // eliminate self-itersecting loops
1505 // trim outside edges
1506 gather_strokes();
1507 for (int index = 0; index < fStrokes.count(); ++index) {
1508 SkPath& outPath = fStrokes[index].fPath;
1509 for (int inner = 0; inner < fStrokes.count(); ++inner) {
1510 if (index == inner) {
1511 continue;
1512 }
1513 SkPath& inPath = fStrokes[inner].fPath;
1514 if (!outPath.getBounds().intersects(inPath.getBounds())) {
1515 continue;
1516 }
Ben Wagner63fd7602017-10-09 15:45:33 -04001517
caryclark64022c12016-05-27 05:13:26 -07001518 }
1519 }
1520 }
1521
1522 void onDrawContent(SkCanvas* canvas) override {
1523#if 0
1524 SkDEBUGCODE(SkDebugStrokeGlobals debugGlobals);
1525 SkOpAA aaResult(fPath, fWidthControl.fValLo, fResControl.fValLo
1526 SkDEBUGPARAMS(&debugGlobals));
1527#endif
1528 SkPath strokePath;
1529// aaResult.simplify(&strokePath);
1530 canvas->drawPath(strokePath, fSkeletonPaint);
1531 SkRect bounds = fPath.getBounds();
1532 SkScalar radius = fWidthControl.fValLo;
1533 int w = (int) (bounds.fRight + radius + 1);
1534 int h = (int) (bounds.fBottom + radius + 1);
1535 SkImageInfo imageInfo = SkImageInfo::MakeA8(w, h);
1536 SkBitmap distMap;
1537 uint8_t* distanceMap = set_up_dist_map(imageInfo, &distMap);
1538 path_coverage(fPath, distanceMap, w, h);
1539 if (fFillButton.enabled()) {
1540 canvas->drawPath(fPath, fCoveragePaint);
1541 }
1542 if (fFilterButton.fState == 2
1543 && (0 < fFilterControl.fValLo || fFilterControl.fValHi < 255)) {
1544 SkBitmap filteredMap;
1545 uint8_t* filtered = set_up_dist_map(imageInfo, &filteredMap);
1546 filter_coverage(distanceMap, sizeof(uint8_t) * w * h, (uint8_t) fFilterControl.fValLo,
1547 (uint8_t) fFilterControl.fValHi, filtered);
1548 canvas->drawBitmap(filteredMap, 0, 0, &fCoveragePaint);
1549 } else if (fFilterButton.enabled()) {
1550 canvas->drawBitmap(distMap, 0, 0, &fCoveragePaint);
1551 }
1552 if (fSkeletonButton.enabled()) {
1553 canvas->drawPath(fPath, fActiveVerb >= 0 ? fLightSkeletonPaint : fSkeletonPaint);
1554 }
1555 if (fActiveVerb >= 0) {
1556 draw_segment(canvas);
1557 }
1558 if (fBisectButton.enabled() || fJoinButton.enabled()) {
1559 draw_bisects(canvas, fActiveVerb >= 0);
1560 }
1561 if (fInOutButton.enabled()) {
1562 if (fActiveVerb >= 0) {
1563 draw_stroke(canvas, fActiveVerb);
1564 } else {
1565 for (int index = 0; index < fPath.countVerbs(); ++index) {
1566 draw_stroke(canvas, index);
1567 }
1568 }
1569 }
1570 if (fHideAll) {
1571 return;
1572 }
1573 for (int index = 0; index < kControlCount; ++index) {
1574 kControlList[index].fControl->draw(canvas, fControlPaints);
1575 }
1576 for (int index = 0; index < kButtonCount; ++index) {
1577 kButtonList[index].fButton->draw(canvas, fButtonPaints);
1578 }
1579 if (fShowLegend) {
1580 draw_legend(canvas);
1581 }
1582
1583#if 0
1584 SkPaint paint;
1585 paint.setARGB(255, 34, 31, 31);
1586 paint.setAntiAlias(true);
1587
1588 SkPath path;
1589 path.moveTo(18,439);
1590 path.lineTo(414,439);
1591 path.lineTo(414,702);
1592 path.lineTo(18,702);
1593 path.lineTo(18,439);
1594
1595 path.moveTo(19,701);
1596 path.lineTo(413,701);
1597 path.lineTo(413,440);
1598 path.lineTo(19,440);
1599 path.lineTo(19,701);
1600 path.close();
1601 canvas->drawPath(path, paint);
1602
1603 canvas->scale(1.0f, -1.0f);
1604 canvas->translate(0.0f, -800.0f);
1605 canvas->drawPath(path, paint);
1606#endif
1607
1608 }
1609
1610 int hittest_pt(SkPoint pt) {
1611 for (int index = 0; index < fPath.countPoints(); ++index) {
1612 if (SkPoint::Distance(fPath.getPoint(index), pt) <= kHitToleranace * 2) {
1613 return index;
1614 }
1615 }
1616 return -1;
1617 }
1618
1619 virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
1620 SkPoint pt = {x, y};
1621 int ptHit = hittest_pt(pt);
1622 if (ptHit >= 0) {
1623 return new MyClick(this, MyClick::kPtType, ptHit);
1624 }
1625 SkPath::Verb verb;
1626 SkScalar weight;
1627 int verbHit = hittest_verb(pt, &verb, &weight);
1628 if (verbHit >= 0) {
1629 return new MyClick(this, MyClick::kVerbType, verbHit, verb, weight);
1630 }
1631 if (!fHideAll) {
1632 const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
1633 for (int index = 0; index < kControlCount; ++index) {
1634 if (kControlList[index].fControl->contains(rectPt)) {
1635 return new MyClick(this, MyClick::kControlType,
1636 kControlList[index].fControlType);
1637 }
1638 }
1639 for (int index = 0; index < kButtonCount; ++index) {
1640 if (kButtonList[index].fButton->contains(rectPt)) {
1641 return new MyClick(this, MyClick::kControlType, kButtonList[index].fButtonType);
1642 }
1643 }
1644 }
1645 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1646 = fCubicButton.fVisible = fWeightControl.fVisible = fAddButton.fVisible
1647 = fDeleteButton.fVisible = false;
1648 fActiveVerb = -1;
1649 fActivePt = -1;
1650 if (fHandlePathMove) {
1651 return new MyClick(this, MyClick::kPathType, MyClick::kPathMove);
1652 }
1653 return this->INHERITED::onFindClickHandler(x, y, modi);
1654 }
1655
1656 static SkScalar MapScreenYtoValue(int y, const UniControl& control) {
1657 return SkTMin(1.f, SkTMax(0.f,
1658 SkIntToScalar(y) - control.fBounds.fTop) / control.fBounds.height())
1659 * (control.fMax - control.fMin) + control.fMin;
1660 }
1661
1662 bool onClick(Click* click) override {
1663 MyClick* myClick = (MyClick*) click;
1664 switch (myClick->fType) {
1665 case MyClick::kPtType: {
1666 savePath(click->fState);
1667 fActivePt = myClick->ptHit();
1668 SkPoint pt = fPath.getPoint((int) myClick->fControl);
1669 pt.offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX),
1670 SkIntToScalar(click->fICurr.fY - click->fIPrev.fY));
1671 set_path_pt(fActivePt, pt, &fPath);
1672 validatePath();
Ben Wagnera93a14a2017-08-28 10:34:05 -04001673 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -07001674 return true;
1675 }
1676 case MyClick::kPathType:
1677 savePath(click->fState);
1678 fPath.offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX),
1679 SkIntToScalar(click->fICurr.fY - click->fIPrev.fY));
1680 validatePath();
Ben Wagnera93a14a2017-08-28 10:34:05 -04001681 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -07001682 return true;
1683 case MyClick::kVerbType: {
1684 fActiveVerb = myClick->verbHit();
1685 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1686 = fCubicButton.fVisible = fAddButton.fVisible = fDeleteButton.fVisible
1687 = true;
1688 fLineButton.setEnabled(myClick->fVerb == SkPath::kLine_Verb);
1689 fQuadButton.setEnabled(myClick->fVerb == SkPath::kQuad_Verb);
1690 fConicButton.setEnabled(myClick->fVerb == SkPath::kConic_Verb);
1691 fCubicButton.setEnabled(myClick->fVerb == SkPath::kCubic_Verb);
1692 fWeightControl.fValLo = myClick->fWeight;
1693 fWeightControl.fVisible = myClick->fVerb == SkPath::kConic_Verb;
1694 } break;
1695 case MyClick::kControlType: {
1696 if (click->fState != Click::kDown_State && myClick->isButton()) {
1697 return true;
1698 }
1699 switch (myClick->fControl) {
1700 case MyClick::kFilterControl: {
1701 SkScalar val = MapScreenYtoValue(click->fICurr.fY, fFilterControl);
1702 if (val - fFilterControl.fValLo < fFilterControl.fValHi - val) {
1703 fFilterControl.fValLo = SkTMax(0.f, val);
1704 } else {
1705 fFilterControl.fValHi = SkTMin(255.f, val);
1706 }
1707 } break;
1708 case MyClick::kResControl:
1709 fResControl.fValLo = MapScreenYtoValue(click->fICurr.fY, fResControl);
1710 break;
1711 case MyClick::kWeightControl: {
1712 savePath(click->fState);
1713 SkScalar w = MapScreenYtoValue(click->fICurr.fY, fWeightControl);
1714 set_path_weight(fActiveVerb, w, &fPath);
1715 validatePath();
1716 fWeightControl.fValLo = w;
1717 } break;
1718 case MyClick::kWidthControl:
1719 fWidthControl.fValLo = MapScreenYtoValue(click->fICurr.fY, fWidthControl);
1720 break;
1721 case MyClick::kLineButton:
1722 savePath(click->fState);
1723 enable_verb_button(myClick->fControl);
1724 fWeightControl.fVisible = false;
1725 set_path_verb(fActiveVerb, SkPath::kLine_Verb, &fPath, 1);
1726 validatePath();
1727 break;
1728 case MyClick::kQuadButton:
1729 savePath(click->fState);
1730 enable_verb_button(myClick->fControl);
1731 fWeightControl.fVisible = false;
1732 set_path_verb(fActiveVerb, SkPath::kQuad_Verb, &fPath, 1);
1733 validatePath();
1734 break;
1735 case MyClick::kConicButton: {
1736 savePath(click->fState);
1737 enable_verb_button(myClick->fControl);
1738 fWeightControl.fVisible = true;
1739 const SkScalar defaultConicWeight = 1.f / SkScalarSqrt(2);
1740 set_path_verb(fActiveVerb, SkPath::kConic_Verb, &fPath, defaultConicWeight);
1741 validatePath();
1742 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1743 } break;
1744 case MyClick::kCubicButton:
1745 savePath(click->fState);
1746 enable_verb_button(myClick->fControl);
1747 fWeightControl.fVisible = false;
1748 set_path_verb(fActiveVerb, SkPath::kCubic_Verb, &fPath, 1);
1749 validatePath();
1750 break;
1751 case MyClick::kAddButton:
1752 savePath(click->fState);
1753 add_path_segment(fActiveVerb, &fPath);
1754 validatePath();
1755 if (fWeightControl.fVisible) {
1756 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1757 }
1758 break;
1759 case MyClick::kDeleteButton:
1760 savePath(click->fState);
1761 delete_path_segment(fActiveVerb, &fPath);
1762 validatePath();
1763 break;
1764 case MyClick::kFillButton:
1765 fFillButton.toggle();
1766 break;
1767 case MyClick::kSkeletonButton:
1768 fSkeletonButton.toggle();
1769 break;
1770 case MyClick::kFilterButton:
1771 fFilterButton.toggle();
1772 fFilterControl.fVisible = fFilterButton.fState == 2;
1773 break;
1774 case MyClick::kBisectButton:
1775 fBisectButton.toggle();
1776 break;
1777 case MyClick::kJoinButton:
1778 fJoinButton.toggle();
1779 break;
1780 case MyClick::kInOutButton:
1781 fInOutButton.toggle();
1782 break;
1783 default:
1784 SkASSERT(0);
1785 break;
1786 }
1787 } break;
1788 default:
1789 SkASSERT(0);
1790 break;
1791 }
1792 setControlButtonsPos();
Ben Wagnera93a14a2017-08-28 10:34:05 -04001793 this->inval(nullptr);
caryclark64022c12016-05-27 05:13:26 -07001794 return true;
1795 }
1796
1797private:
1798 typedef SampleView INHERITED;
1799};
1800
1801static struct KeyCommand {
1802 char fKey;
1803 char fAlternate;
1804 const char* fDescriptionL;
1805 const char* fDescriptionR;
1806 bool (AAGeometryView::*fFunction)();
1807} kKeyCommandList[] = {
Ben Wagner63fd7602017-10-09 15:45:33 -04001808 { ' ', 0, "space", "center path", &AAGeometryView::scaleToFit },
1809 { '-', 0, "-", "zoom out", &AAGeometryView::scaleDown },
1810 { '+', '=', "+/=", "zoom in", &AAGeometryView::scaleUp },
Chris Dalton08d1a252017-10-20 11:46:47 -06001811 { 'D', 0, "D", "dump to console", &AAGeometryView::pathDump },
1812 { 'H', 0, "H", "hide controls", &AAGeometryView::hideAll },
1813 { 'R', 0, "R", "reset path", &AAGeometryView::constructPath },
1814 { 'Z', 0, "Z", "undo", &AAGeometryView::undo },
caryclark64022c12016-05-27 05:13:26 -07001815 { '?', 0, "?", "show legend", &AAGeometryView::showLegend },
1816};
1817
1818const int kKeyCommandCount = (int) SK_ARRAY_COUNT(kKeyCommandList);
1819
1820void AAGeometryView::draw_legend(SkCanvas* canvas) {
1821 SkScalar bottomOffset = this->height() - 10;
1822 for (int index = kKeyCommandCount - 1; index >= 0; --index) {
1823 bottomOffset -= 15;
Cary Clark2a475ea2017-04-28 15:35:12 -04001824 canvas->drawString(kKeyCommandList[index].fDescriptionL,
1825 this->width() - 160, bottomOffset,
caryclark64022c12016-05-27 05:13:26 -07001826 fLegendLeftPaint);
Cary Clark2a475ea2017-04-28 15:35:12 -04001827 canvas->drawString(kKeyCommandList[index].fDescriptionR,
1828 this->width() - 20, bottomOffset,
caryclark64022c12016-05-27 05:13:26 -07001829 fLegendRightPaint);
1830 }
1831}
1832
1833// overrides from SkEventSink
1834bool AAGeometryView::onQuery(SkEvent* evt) {
1835 if (SampleCode::TitleQ(*evt)) {
1836 SampleCode::TitleR(evt, "AAGeometry");
1837 return true;
1838 }
1839 SkUnichar uni;
1840 if (false) {
1841 return this->INHERITED::onQuery(evt);
1842 }
1843 if (SampleCode::CharQ(*evt, &uni)) {
1844 for (int index = 0; index < kButtonCount; ++index) {
1845 Button* button = kButtonList[index].fButton;
1846 if (button->fVisible && uni == button->fLabel) {
1847 MyClick click(this, MyClick::kControlType, kButtonList[index].fButtonType);
1848 click.fState = Click::kDown_State;
1849 (void) this->onClick(&click);
1850 return true;
1851 }
1852 }
1853 for (int index = 0; index < kKeyCommandCount; ++index) {
1854 KeyCommand& keyCommand = kKeyCommandList[index];
1855 if (uni == keyCommand.fKey || uni == keyCommand.fAlternate) {
1856 return (this->*keyCommand.fFunction)();
1857 }
1858 }
1859 if (('A' <= uni && uni <= 'Z') || ('a' <= uni && uni <= 'z')) {
1860 for (int index = 0; index < kButtonCount; ++index) {
1861 Button* button = kButtonList[index].fButton;
1862 if (button->fVisible && (uni & ~0x20) == (button->fLabel & ~0x20)) {
1863 MyClick click(this, MyClick::kControlType, kButtonList[index].fButtonType);
1864 click.fState = Click::kDown_State;
1865 (void) this->onClick(&click);
1866 return true;
1867 }
1868 }
1869 }
1870 }
1871 return this->INHERITED::onQuery(evt);
1872}
Ben Wagner63fd7602017-10-09 15:45:33 -04001873
caryclark64022c12016-05-27 05:13:26 -07001874DEF_SAMPLE( return new AAGeometryView; )