• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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 "GrShape.h"
9 
operator =(const GrShape & that)10 GrShape& GrShape::operator=(const GrShape& that) {
11     fStyle = that.fStyle;
12     this->changeType(that.fType, Type::kPath == that.fType ? &that.path() : nullptr);
13     switch (fType) {
14         case Type::kEmpty:
15             break;
16         case Type::kRRect:
17             fRRectData = that.fRRectData;
18             break;
19         case Type::kLine:
20             fLineData = that.fLineData;
21             break;
22         case Type::kPath:
23             fPathData.fGenID = that.fPathData.fGenID;
24             break;
25     }
26     fInheritedKey.reset(that.fInheritedKey.count());
27     sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
28                       sizeof(uint32_t) * fInheritedKey.count());
29     return *this;
30 }
31 
bounds() const32 SkRect GrShape::bounds() const {
33     // Bounds where left == bottom or top == right can indicate a line or point shape. We return
34     // inverted bounds for a truly empty shape.
35     static constexpr SkRect kInverted = SkRect::MakeLTRB(1, 1, -1, -1);
36     switch (fType) {
37         case Type::kEmpty:
38             return kInverted;
39         case Type::kLine: {
40             SkRect bounds;
41             if (fLineData.fPts[0].fX < fLineData.fPts[1].fX) {
42                 bounds.fLeft = fLineData.fPts[0].fX;
43                 bounds.fRight = fLineData.fPts[1].fX;
44             } else {
45                 bounds.fLeft = fLineData.fPts[1].fX;
46                 bounds.fRight = fLineData.fPts[0].fX;
47             }
48             if (fLineData.fPts[0].fY < fLineData.fPts[1].fY) {
49                 bounds.fTop = fLineData.fPts[0].fY;
50                 bounds.fBottom = fLineData.fPts[1].fY;
51             } else {
52                 bounds.fTop = fLineData.fPts[1].fY;
53                 bounds.fBottom = fLineData.fPts[0].fY;
54             }
55             return bounds;
56         }
57         case Type::kRRect:
58             return fRRectData.fRRect.getBounds();
59         case Type::kPath:
60             return this->path().getBounds();
61     }
62     SkFAIL("Unknown shape type");
63     return kInverted;
64 }
65 
styledBounds() const66 SkRect GrShape::styledBounds() const {
67     if (Type::kEmpty == fType && !fStyle.hasNonDashPathEffect()) {
68         return SkRect::MakeEmpty();
69     }
70     SkRect bounds;
71     fStyle.adjustBounds(&bounds, this->bounds());
72     return bounds;
73 }
74 
75 // If the path is small enough to be keyed from its data this returns key length, otherwise -1.
path_key_from_data_size(const SkPath & path)76 static int path_key_from_data_size(const SkPath& path) {
77     const int verbCnt = path.countVerbs();
78     if (verbCnt > GrShape::kMaxKeyFromDataVerbCnt) {
79         return -1;
80     }
81     const int pointCnt = path.countPoints();
82     const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
83 
84     GR_STATIC_ASSERT(sizeof(SkPoint) == 2 * sizeof(uint32_t));
85     GR_STATIC_ASSERT(sizeof(SkScalar) == sizeof(uint32_t));
86     // 2 is for the verb cnt and a fill type. Each verb is a byte but we'll pad the verb data out to
87     // a uint32_t length.
88     return 2 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
89 }
90 
91 // Writes the path data key into the passed pointer.
write_path_key_from_data(const SkPath & path,uint32_t * origKey)92 static void write_path_key_from_data(const SkPath& path, uint32_t* origKey) {
93     uint32_t* key = origKey;
94     // The check below should take care of negative values casted positive.
95     const int verbCnt = path.countVerbs();
96     const int pointCnt = path.countPoints();
97     const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
98     SkASSERT(verbCnt <= GrShape::kMaxKeyFromDataVerbCnt);
99     SkASSERT(pointCnt && verbCnt);
100     *key++ = path.getFillType();
101     *key++ = verbCnt;
102     memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t));
103     int verbKeySize = SkAlign4(verbCnt);
104     // pad out to uint32_t alignment using value that will stand out when debugging.
105     uint8_t* pad = reinterpret_cast<uint8_t*>(key)+ verbCnt;
106     memset(pad, 0xDE, verbKeySize - verbCnt);
107     key += verbKeySize >> 2;
108 
109     memcpy(key, SkPathPriv::PointData(path), sizeof(SkPoint) * pointCnt);
110     GR_STATIC_ASSERT(sizeof(SkPoint) == 2 * sizeof(uint32_t));
111     key += 2 * pointCnt;
112     sk_careful_memcpy(key, SkPathPriv::ConicWeightData(path), sizeof(SkScalar) * conicWeightCnt);
113     GR_STATIC_ASSERT(sizeof(SkScalar) == sizeof(uint32_t));
114     SkDEBUGCODE(key += conicWeightCnt);
115     SkASSERT(key - origKey == path_key_from_data_size(path));
116 }
117 
unstyledKeySize() const118 int GrShape::unstyledKeySize() const {
119     if (fInheritedKey.count()) {
120         return fInheritedKey.count();
121     }
122     switch (fType) {
123         case Type::kEmpty:
124             return 1;
125         case Type::kRRect:
126             SkASSERT(!fInheritedKey.count());
127             SkASSERT(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
128             // + 1 for the direction, start index, and inverseness.
129             return SkRRect::kSizeInMemory / sizeof(uint32_t) + 1;
130         case Type::kLine:
131             GR_STATIC_ASSERT(2 * sizeof(uint32_t) == sizeof(SkPoint));
132             // 4 for the end points and 1 for the inverseness
133             return 5;
134         case Type::kPath: {
135             if (0 == fPathData.fGenID) {
136                 return -1;
137             }
138             int dataKeySize = path_key_from_data_size(fPathData.fPath);
139             if (dataKeySize >= 0) {
140                 return dataKeySize;
141             }
142             // The key is the path ID and fill type.
143             return 2;
144         }
145     }
146     SkFAIL("Should never get here.");
147     return 0;
148 }
149 
writeUnstyledKey(uint32_t * key) const150 void GrShape::writeUnstyledKey(uint32_t* key) const {
151     SkASSERT(this->unstyledKeySize());
152     SkDEBUGCODE(uint32_t* origKey = key;)
153     if (fInheritedKey.count()) {
154         memcpy(key, fInheritedKey.get(), sizeof(uint32_t) * fInheritedKey.count());
155         SkDEBUGCODE(key += fInheritedKey.count();)
156     } else {
157         switch (fType) {
158             case Type::kEmpty:
159                 *key++ = 1;
160                 break;
161             case Type::kRRect:
162                 fRRectData.fRRect.writeToMemory(key);
163                 key += SkRRect::kSizeInMemory / sizeof(uint32_t);
164                 *key = (fRRectData.fDir == SkPath::kCCW_Direction) ? (1 << 31) : 0;
165                 *key |= fRRectData.fInverted ? (1 << 30) : 0;
166                 *key++ |= fRRectData.fStart;
167                 SkASSERT(fRRectData.fStart < 8);
168                 break;
169             case Type::kLine:
170                 memcpy(key, fLineData.fPts, 2 * sizeof(SkPoint));
171                 key += 4;
172                 *key++ = fLineData.fInverted ? 1 : 0;
173                 break;
174             case Type::kPath: {
175                 SkASSERT(fPathData.fGenID);
176                 int dataKeySize = path_key_from_data_size(fPathData.fPath);
177                 if (dataKeySize >= 0) {
178                     write_path_key_from_data(fPathData.fPath, key);
179                     return;
180                 }
181                 *key++ = fPathData.fGenID;
182                 // We could canonicalize the fill rule for paths that don't differentiate between
183                 // even/odd or winding fill (e.g. convex).
184                 *key++ = this->path().getFillType();
185                 break;
186             }
187         }
188     }
189     SkASSERT(key - origKey == this->unstyledKeySize());
190 }
191 
setInheritedKey(const GrShape & parent,GrStyle::Apply apply,SkScalar scale)192 void GrShape::setInheritedKey(const GrShape &parent, GrStyle::Apply apply, SkScalar scale) {
193     SkASSERT(!fInheritedKey.count());
194     // If the output shape turns out to be simple, then we will just use its geometric key
195     if (Type::kPath == fType) {
196         // We want ApplyFullStyle(ApplyPathEffect(shape)) to have the same key as
197         // ApplyFullStyle(shape).
198         // The full key is structured as (geo,path_effect,stroke).
199         // If we do ApplyPathEffect we get get,path_effect as the inherited key. If we then
200         // do ApplyFullStyle we'll memcpy geo,path_effect into the new inherited key
201         // and then append the style key (which should now be stroke only) at the end.
202         int parentCnt = parent.fInheritedKey.count();
203         bool useParentGeoKey = !parentCnt;
204         if (useParentGeoKey) {
205             parentCnt = parent.unstyledKeySize();
206             if (parentCnt < 0) {
207                 // The parent's geometry has no key so we will have no key.
208                 fPathData.fGenID = 0;
209                 return;
210             }
211         }
212         uint32_t styleKeyFlags = 0;
213         if (parent.knownToBeClosed()) {
214             styleKeyFlags |= GrStyle::kClosed_KeyFlag;
215         }
216         if (parent.asLine(nullptr, nullptr)) {
217             styleKeyFlags |= GrStyle::kNoJoins_KeyFlag;
218         }
219         int styleCnt = GrStyle::KeySize(parent.fStyle, apply, styleKeyFlags);
220         if (styleCnt < 0) {
221             // The style doesn't allow a key, set the path gen ID to 0 so that we fail when
222             // we try to get a key for the shape.
223             fPathData.fGenID = 0;
224             return;
225         }
226         fInheritedKey.reset(parentCnt + styleCnt);
227         if (useParentGeoKey) {
228             // This will be the geo key.
229             parent.writeUnstyledKey(fInheritedKey.get());
230         } else {
231             // This should be (geo,path_effect).
232             memcpy(fInheritedKey.get(), parent.fInheritedKey.get(),
233                    parentCnt * sizeof(uint32_t));
234         }
235         // Now turn (geo,path_effect) or (geo) into (geo,path_effect,stroke)
236         GrStyle::WriteKey(fInheritedKey.get() + parentCnt, parent.fStyle, apply, scale,
237                           styleKeyFlags);
238     }
239 }
240 
GrShape(const GrShape & that)241 GrShape::GrShape(const GrShape& that) : fStyle(that.fStyle) {
242     const SkPath* thatPath = Type::kPath == that.fType ? &that.fPathData.fPath : nullptr;
243     this->initType(that.fType, thatPath);
244     switch (fType) {
245         case Type::kEmpty:
246             break;
247         case Type::kRRect:
248             fRRectData = that.fRRectData;
249             break;
250         case Type::kLine:
251             fLineData = that.fLineData;
252             break;
253         case Type::kPath:
254             fPathData.fGenID = that.fPathData.fGenID;
255             break;
256     }
257     fInheritedKey.reset(that.fInheritedKey.count());
258     sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
259                       sizeof(uint32_t) * fInheritedKey.count());
260 }
261 
GrShape(const GrShape & parent,GrStyle::Apply apply,SkScalar scale)262 GrShape::GrShape(const GrShape& parent, GrStyle::Apply apply, SkScalar scale) {
263     // TODO: Add some quantization of scale for better cache performance here or leave that up
264     // to caller?
265     // TODO: For certain shapes and stroke params we could ignore the scale. (e.g. miter or bevel
266     // stroke of a rect).
267     if (!parent.style().applies() ||
268         (GrStyle::Apply::kPathEffectOnly == apply && !parent.style().pathEffect())) {
269         this->initType(Type::kEmpty);
270         *this = parent;
271         return;
272     }
273 
274     SkPathEffect* pe = parent.fStyle.pathEffect();
275     SkTLazy<SkPath> tmpPath;
276     const GrShape* parentForKey = &parent;
277     SkTLazy<GrShape> tmpParent;
278     this->initType(Type::kPath);
279     fPathData.fGenID = 0;
280     if (pe) {
281         const SkPath* srcForPathEffect;
282         if (parent.fType == Type::kPath) {
283             srcForPathEffect = &parent.path();
284         } else {
285             srcForPathEffect = tmpPath.init();
286             parent.asPath(tmpPath.get());
287         }
288         // Should we consider bounds? Would have to include in key, but it'd be nice to know
289         // if the bounds actually modified anything before including in key.
290         SkStrokeRec strokeRec = parent.fStyle.strokeRec();
291         if (!parent.fStyle.applyPathEffectToPath(&this->path(), &strokeRec, *srcForPathEffect,
292                                                  scale)) {
293             tmpParent.init(*srcForPathEffect, GrStyle(strokeRec, nullptr));
294             *this = tmpParent.get()->applyStyle(apply, scale);
295             return;
296         }
297         // A path effect has access to change the res scale but we aren't expecting it to and it
298         // would mess up our key computation.
299         SkASSERT(scale == strokeRec.getResScale());
300         if (GrStyle::Apply::kPathEffectAndStrokeRec == apply && strokeRec.needToApply()) {
301             // The intermediate shape may not be a general path. If we we're just applying
302             // the path effect then attemptToReduceFromPath would catch it. This means that
303             // when we subsequently applied the remaining strokeRec we would have a non-path
304             // parent shape that would be used to determine the the stroked path's key.
305             // We detect that case here and change parentForKey to a temporary that represents
306             // the simpler shape so that applying both path effect and the strokerec all at
307             // once produces the same key.
308             tmpParent.init(this->path(), GrStyle(strokeRec, nullptr));
309             tmpParent.get()->setInheritedKey(parent, GrStyle::Apply::kPathEffectOnly, scale);
310             if (!tmpPath.isValid()) {
311                 tmpPath.init();
312             }
313             tmpParent.get()->asPath(tmpPath.get());
314             SkStrokeRec::InitStyle fillOrHairline;
315             // The parent shape may have simplified away the strokeRec, check for that here.
316             if (tmpParent.get()->style().applies()) {
317                 SkAssertResult(tmpParent.get()->style().applyToPath(&this->path(), &fillOrHairline,
318                                                                     *tmpPath.get(), scale));
319             } else if (tmpParent.get()->style().isSimpleFill()) {
320                 fillOrHairline = SkStrokeRec::kFill_InitStyle;
321             } else {
322                 SkASSERT(tmpParent.get()->style().isSimpleHairline());
323                 fillOrHairline = SkStrokeRec::kHairline_InitStyle;
324             }
325             fStyle.resetToInitStyle(fillOrHairline);
326             parentForKey = tmpParent.get();
327         } else {
328             fStyle = GrStyle(strokeRec, nullptr);
329         }
330     } else {
331         const SkPath* srcForParentStyle;
332         if (parent.fType == Type::kPath) {
333             srcForParentStyle = &parent.path();
334         } else {
335             srcForParentStyle = tmpPath.init();
336             parent.asPath(tmpPath.get());
337         }
338         SkStrokeRec::InitStyle fillOrHairline;
339         SkASSERT(parent.fStyle.applies());
340         SkASSERT(!parent.fStyle.pathEffect());
341         SkAssertResult(parent.fStyle.applyToPath(&this->path(), &fillOrHairline, *srcForParentStyle,
342                                                  scale));
343         fStyle.resetToInitStyle(fillOrHairline);
344     }
345     this->attemptToSimplifyPath();
346     this->setInheritedKey(*parentForKey, apply, scale);
347 }
348 
attemptToSimplifyPath()349 void GrShape::attemptToSimplifyPath() {
350     SkRect rect;
351     SkRRect rrect;
352     SkPath::Direction rrectDir;
353     unsigned rrectStart;
354     bool inverted = this->path().isInverseFillType();
355     SkPoint pts[2];
356     if (this->path().isEmpty()) {
357         this->changeType(Type::kEmpty);
358     } else if (this->path().isLine(pts)) {
359         this->changeType(Type::kLine);
360         fLineData.fPts[0] = pts[0];
361         fLineData.fPts[1] = pts[1];
362         fLineData.fInverted = inverted;
363     } else if (this->path().isRRect(&rrect, &rrectDir, &rrectStart)) {
364         this->changeType(Type::kRRect);
365         fRRectData.fRRect = rrect;
366         fRRectData.fDir = rrectDir;
367         fRRectData.fStart = rrectStart;
368         fRRectData.fInverted = inverted;
369         // Currently SkPath does not acknowledge that empty, rect, or oval subtypes as rrects.
370         SkASSERT(!fRRectData.fRRect.isEmpty());
371         SkASSERT(fRRectData.fRRect.getType() != SkRRect::kRect_Type);
372         SkASSERT(fRRectData.fRRect.getType() != SkRRect::kOval_Type);
373     } else if (this->path().isOval(&rect, &rrectDir, &rrectStart)) {
374         this->changeType(Type::kRRect);
375         fRRectData.fRRect.setOval(rect);
376         fRRectData.fDir = rrectDir;
377         fRRectData.fInverted = inverted;
378         // convert from oval indexing to rrect indexiing.
379         fRRectData.fStart = 2 * rrectStart;
380     } else if (SkPathPriv::IsSimpleClosedRect(this->path(), &rect, &rrectDir, &rrectStart)) {
381         this->changeType(Type::kRRect);
382         // When there is a path effect we restrict rect detection to the narrower API that
383         // gives us the starting position. Otherwise, we will retry with the more aggressive
384         // isRect().
385         fRRectData.fRRect.setRect(rect);
386         fRRectData.fInverted = inverted;
387         fRRectData.fDir = rrectDir;
388         // convert from rect indexing to rrect indexiing.
389         fRRectData.fStart = 2 * rrectStart;
390     } else if (!this->style().hasPathEffect()) {
391         bool closed;
392         if (this->path().isRect(&rect, &closed, nullptr)) {
393             if (closed || this->style().isSimpleFill()) {
394                 this->changeType(Type::kRRect);
395                 fRRectData.fRRect.setRect(rect);
396                 // Since there is no path effect the dir and start index is immaterial.
397                 fRRectData.fDir = kDefaultRRectDir;
398                 fRRectData.fStart = kDefaultRRectStart;
399                 // There isn't dashing so we will have to preserver inverseness.
400                 fRRectData.fInverted = inverted;
401             }
402         }
403     }
404     if (Type::kPath != fType) {
405         fInheritedKey.reset(0);
406         if (Type::kRRect == fType) {
407             this->attemptToSimplifyRRect();
408         } else if (Type::kLine == fType) {
409             this->attemptToSimplifyLine();
410         }
411     } else {
412         if (fInheritedKey.count() || this->path().isVolatile()) {
413             fPathData.fGenID = 0;
414         } else {
415             fPathData.fGenID = this->path().getGenerationID();
416         }
417         if (!this->style().hasNonDashPathEffect()) {
418             if (this->style().strokeRec().getStyle() == SkStrokeRec::kStroke_Style ||
419                 this->style().strokeRec().getStyle() == SkStrokeRec::kHairline_Style) {
420                 // Stroke styles don't differentiate between winding and even/odd.
421                 // Moreover, dashing ignores inverseness (skbug.com/5421)
422                 bool inverse = !this->style().isDashed() && this->path().isInverseFillType();
423                 if (inverse) {
424                     this->path().setFillType(kDefaultPathInverseFillType);
425                 } else {
426                     this->path().setFillType(kDefaultPathFillType);
427                 }
428             } else if (this->path().isConvex()) {
429                 // There is no distinction between even/odd and non-zero winding count for convex
430                 // paths.
431                 if (this->path().isInverseFillType()) {
432                     this->path().setFillType(kDefaultPathInverseFillType);
433                 } else {
434                     this->path().setFillType(kDefaultPathFillType);
435                 }
436             }
437         }
438     }
439 }
440 
attemptToSimplifyRRect()441 void GrShape::attemptToSimplifyRRect() {
442     SkASSERT(Type::kRRect == fType);
443     SkASSERT(!fInheritedKey.count());
444     if (fRRectData.fRRect.isEmpty()) {
445         fType = Type::kEmpty;
446         return;
447     }
448     if (!this->style().hasPathEffect()) {
449         fRRectData.fDir = kDefaultRRectDir;
450         fRRectData.fStart = kDefaultRRectStart;
451     } else if (fStyle.isDashed()) {
452         // Dashing ignores the inverseness (currently). skbug.com/5421
453         fRRectData.fInverted = false;
454     }
455     // Turn a stroke-and-filled miter rect into a filled rect. TODO: more rrect stroke shortcuts.
456     if (!fStyle.hasPathEffect() &&
457         fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style &&
458         fStyle.strokeRec().getJoin() == SkPaint::kMiter_Join &&
459         fStyle.strokeRec().getMiter() >= SK_ScalarSqrt2 &&
460         fRRectData.fRRect.isRect()) {
461         SkScalar r = fStyle.strokeRec().getWidth() / 2;
462         fRRectData.fRRect = SkRRect::MakeRect(fRRectData.fRRect.rect().makeOutset(r, r));
463         fStyle = GrStyle::SimpleFill();
464     }
465 }
466 
attemptToSimplifyLine()467 void GrShape::attemptToSimplifyLine() {
468     SkASSERT(Type::kLine == fType);
469     SkASSERT(!fInheritedKey.count());
470     if (fStyle.isDashed()) {
471         // Dashing ignores inverseness.
472         fLineData.fInverted = false;
473         return;
474     } else if (fStyle.hasPathEffect()) {
475         return;
476     }
477     if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
478         // Make stroke + fill be stroke since the fill is empty.
479         SkStrokeRec rec = fStyle.strokeRec();
480         rec.setStrokeStyle(fStyle.strokeRec().getWidth(), false);
481         fStyle = GrStyle(rec, nullptr);
482     }
483     if (fStyle.isSimpleFill() && !fLineData.fInverted) {
484         this->changeType(Type::kEmpty);
485         return;
486     }
487     SkPoint* pts = fLineData.fPts;
488     if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style) {
489         // If it is horizontal or vertical we will turn it into a filled rrect.
490         SkRect rect;
491         rect.fLeft = SkTMin(pts[0].fX, pts[1].fX);
492         rect.fRight = SkTMax(pts[0].fX, pts[1].fX);
493         rect.fTop = SkTMin(pts[0].fY, pts[1].fY);
494         rect.fBottom = SkTMax(pts[0].fY, pts[1].fY);
495         bool eqX = rect.fLeft == rect.fRight;
496         bool eqY = rect.fTop == rect.fBottom;
497         if (eqX || eqY) {
498             SkScalar r = fStyle.strokeRec().getWidth() / 2;
499             bool inverted = fLineData.fInverted;
500             this->changeType(Type::kRRect);
501             switch (fStyle.strokeRec().getCap()) {
502                 case SkPaint::kButt_Cap:
503                     if (eqX && eqY) {
504                         this->changeType(Type::kEmpty);
505                         return;
506                     }
507                     if (eqX) {
508                         rect.outset(r, 0);
509                     } else {
510                         rect.outset(0, r);
511                     }
512                     fRRectData.fRRect = SkRRect::MakeRect(rect);
513                     break;
514                 case SkPaint::kSquare_Cap:
515                     rect.outset(r, r);
516                     fRRectData.fRRect = SkRRect::MakeRect(rect);
517                     break;
518                 case SkPaint::kRound_Cap:
519                     rect.outset(r, r);
520                     fRRectData.fRRect = SkRRect::MakeRectXY(rect, r, r);
521                     break;
522             }
523             fRRectData.fInverted = inverted;
524             fRRectData.fDir = kDefaultRRectDir;
525             fRRectData.fStart = kDefaultRRectStart;
526             if (fRRectData.fRRect.isEmpty()) {
527                 // This can happen when r is very small relative to the rect edges.
528                 this->changeType(Type::kEmpty);
529                 return;
530             }
531             fStyle = GrStyle::SimpleFill();
532             return;
533         }
534     }
535     // Only path effects could care about the order of the points. Otherwise canonicalize
536     // the point order.
537     if (pts[1].fY < pts[0].fY || (pts[1].fY == pts[0].fY && pts[1].fX < pts[0].fX)) {
538         SkTSwap(pts[0], pts[1]);
539     }
540 }
541