1 /*
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 "src/gpu/geometry/GrAAConvexTessellator.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkPath.h"
12 #include "include/core/SkPoint.h"
13 #include "include/core/SkString.h"
14 #include "include/private/SkTPin.h"
15 #include "src/gpu/geometry/GrPathUtils.h"
16
17 // Next steps:
18 // add an interactive sample app slide
19 // add debug check that all points are suitably far apart
20 // test more degenerate cases
21
22 // The tolerance for fusing vertices and eliminating colinear lines (It is in device space).
23 static const SkScalar kClose = (SK_Scalar1 / 16);
24 static const SkScalar kCloseSqd = kClose * kClose;
25
26 // tesselation tolerance values, in device space pixels
27 static const SkScalar kQuadTolerance = 0.2f;
28 static const SkScalar kCubicTolerance = 0.2f;
29 static const SkScalar kConicTolerance = 0.25f;
30
31 // dot product below which we use a round cap between curve segments
32 static const SkScalar kRoundCapThreshold = 0.8f;
33
34 // dot product above which we consider two adjacent curves to be part of the "same" curve
35 static const SkScalar kCurveConnectionThreshold = 0.8f;
36
intersect(const SkPoint & p0,const SkPoint & n0,const SkPoint & p1,const SkPoint & n1,SkScalar * t)37 static bool intersect(const SkPoint& p0, const SkPoint& n0,
38 const SkPoint& p1, const SkPoint& n1,
39 SkScalar* t) {
40 const SkPoint v = p1 - p0;
41 SkScalar perpDot = n0.fX * n1.fY - n0.fY * n1.fX;
42 if (SkScalarNearlyZero(perpDot)) {
43 return false;
44 }
45 *t = (v.fX * n1.fY - v.fY * n1.fX) / perpDot;
46 return SkScalarIsFinite(*t);
47 }
48
49 // This is a special case version of intersect where we have the vector
50 // perpendicular to the second line rather than the vector parallel to it.
perp_intersect(const SkPoint & p0,const SkPoint & n0,const SkPoint & p1,const SkPoint & perp)51 static SkScalar perp_intersect(const SkPoint& p0, const SkPoint& n0,
52 const SkPoint& p1, const SkPoint& perp) {
53 const SkPoint v = p1 - p0;
54 SkScalar perpDot = n0.dot(perp);
55 return v.dot(perp) / perpDot;
56 }
57
duplicate_pt(const SkPoint & p0,const SkPoint & p1)58 static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
59 SkScalar distSq = SkPointPriv::DistanceToSqd(p0, p1);
60 return distSq < kCloseSqd;
61 }
62
points_are_colinear_and_b_is_middle(const SkPoint & a,const SkPoint & b,const SkPoint & c,float * accumError)63 static bool points_are_colinear_and_b_is_middle(const SkPoint& a, const SkPoint& b,
64 const SkPoint& c, float* accumError) {
65 // First check distance from b to the infinite line through a, c
66 SkVector aToC = c - a;
67 SkVector n = {aToC.fY, -aToC.fX};
68 n.normalize();
69
70 SkScalar distBToLineAC = SkScalarAbs(n.dot(b) - n.dot(a));
71 if (*accumError + distBToLineAC >= kClose || aToC.dot(b - a) <= 0.f || aToC.dot(c - b) <= 0.f) {
72 // Too far from the line or not between the line segment from a to c
73 return false;
74 } else {
75 // Accumulate the distance from b to |ac| that goes "away" when this near-colinear point
76 // is removed to simplify the path.
77 *accumError += distBToLineAC;
78 return true;
79 }
80 }
81
addPt(const SkPoint & pt,SkScalar depth,SkScalar coverage,bool movable,CurveState curve)82 int GrAAConvexTessellator::addPt(const SkPoint& pt,
83 SkScalar depth,
84 SkScalar coverage,
85 bool movable,
86 CurveState curve) {
87 SkASSERT(pt.isFinite());
88 this->validate();
89
90 int index = fPts.count();
91 *fPts.push() = pt;
92 *fCoverages.push() = coverage;
93 *fMovable.push() = movable;
94 *fCurveState.push() = curve;
95
96 this->validate();
97 return index;
98 }
99
popLastPt()100 void GrAAConvexTessellator::popLastPt() {
101 this->validate();
102
103 fPts.pop();
104 fCoverages.pop();
105 fMovable.pop();
106 fCurveState.pop();
107
108 this->validate();
109 }
110
popFirstPtShuffle()111 void GrAAConvexTessellator::popFirstPtShuffle() {
112 this->validate();
113
114 fPts.removeShuffle(0);
115 fCoverages.removeShuffle(0);
116 fMovable.removeShuffle(0);
117 fCurveState.removeShuffle(0);
118
119 this->validate();
120 }
121
updatePt(int index,const SkPoint & pt,SkScalar depth,SkScalar coverage)122 void GrAAConvexTessellator::updatePt(int index,
123 const SkPoint& pt,
124 SkScalar depth,
125 SkScalar coverage) {
126 this->validate();
127 SkASSERT(fMovable[index]);
128
129 fPts[index] = pt;
130 fCoverages[index] = coverage;
131 }
132
addTri(int i0,int i1,int i2)133 void GrAAConvexTessellator::addTri(int i0, int i1, int i2) {
134 if (i0 == i1 || i1 == i2 || i2 == i0) {
135 return;
136 }
137
138 *fIndices.push() = i0;
139 *fIndices.push() = i1;
140 *fIndices.push() = i2;
141 }
142
rewind()143 void GrAAConvexTessellator::rewind() {
144 fPts.rewind();
145 fCoverages.rewind();
146 fMovable.rewind();
147 fIndices.rewind();
148 fNorms.rewind();
149 fCurveState.rewind();
150 fInitialRing.rewind();
151 fCandidateVerts.rewind();
152 #if GR_AA_CONVEX_TESSELLATOR_VIZ
153 fRings.rewind(); // TODO: leak in this case!
154 #else
155 fRings[0].rewind();
156 fRings[1].rewind();
157 #endif
158 }
159
computeNormals()160 void GrAAConvexTessellator::computeNormals() {
161 auto normalToVector = [this](SkVector v) {
162 SkVector n = SkPointPriv::MakeOrthog(v, fSide);
163 SkAssertResult(n.normalize());
164 SkASSERT(SkScalarNearlyEqual(1.0f, n.length()));
165 return n;
166 };
167
168 // Check the cross product of the final trio
169 fNorms.append(fPts.count());
170 fNorms[0] = fPts[1] - fPts[0];
171 fNorms.top() = fPts[0] - fPts.top();
172 SkScalar cross = SkPoint::CrossProduct(fNorms[0], fNorms.top());
173 fSide = (cross > 0.0f) ? SkPointPriv::kRight_Side : SkPointPriv::kLeft_Side;
174 fNorms[0] = normalToVector(fNorms[0]);
175 for (int cur = 1; cur < fNorms.count() - 1; ++cur) {
176 fNorms[cur] = normalToVector(fPts[cur + 1] - fPts[cur]);
177 }
178 fNorms.top() = normalToVector(fNorms.top());
179 }
180
computeBisectors()181 void GrAAConvexTessellator::computeBisectors() {
182 fBisectors.setCount(fNorms.count());
183
184 int prev = fBisectors.count() - 1;
185 for (int cur = 0; cur < fBisectors.count(); prev = cur, ++cur) {
186 fBisectors[cur] = fNorms[cur] + fNorms[prev];
187 if (!fBisectors[cur].normalize()) {
188 fBisectors[cur] = SkPointPriv::MakeOrthog(fNorms[cur], (SkPointPriv::Side)-fSide) +
189 SkPointPriv::MakeOrthog(fNorms[prev], fSide);
190 SkAssertResult(fBisectors[cur].normalize());
191 } else {
192 fBisectors[cur].negate(); // make the bisector face in
193 }
194 if (fCurveState[prev] == kIndeterminate_CurveState) {
195 if (fCurveState[cur] == kSharp_CurveState) {
196 fCurveState[prev] = kSharp_CurveState;
197 } else {
198 if (SkScalarAbs(fNorms[cur].dot(fNorms[prev])) > kCurveConnectionThreshold) {
199 fCurveState[prev] = kCurve_CurveState;
200 fCurveState[cur] = kCurve_CurveState;
201 } else {
202 fCurveState[prev] = kSharp_CurveState;
203 fCurveState[cur] = kSharp_CurveState;
204 }
205 }
206 }
207
208 SkASSERT(SkScalarNearlyEqual(1.0f, fBisectors[cur].length()));
209 }
210 }
211
212 // Create as many rings as we need to (up to a predefined limit) to reach the specified target
213 // depth. If we are in fill mode, the final ring will automatically be fanned.
createInsetRings(Ring & previousRing,SkScalar initialDepth,SkScalar initialCoverage,SkScalar targetDepth,SkScalar targetCoverage,Ring ** finalRing)214 bool GrAAConvexTessellator::createInsetRings(Ring& previousRing, SkScalar initialDepth,
215 SkScalar initialCoverage, SkScalar targetDepth,
216 SkScalar targetCoverage, Ring** finalRing) {
217 static const int kMaxNumRings = 8;
218
219 if (previousRing.numPts() < 3) {
220 return false;
221 }
222 Ring* currentRing = &previousRing;
223 int i;
224 for (i = 0; i < kMaxNumRings; ++i) {
225 Ring* nextRing = this->getNextRing(currentRing);
226 SkASSERT(nextRing != currentRing);
227
228 bool done = this->createInsetRing(*currentRing, nextRing, initialDepth, initialCoverage,
229 targetDepth, targetCoverage, i == 0);
230 currentRing = nextRing;
231 if (done) {
232 break;
233 }
234 currentRing->init(*this);
235 }
236
237 if (kMaxNumRings == i) {
238 // Bail if we've exceeded the amount of time we want to throw at this.
239 this->terminate(*currentRing);
240 return false;
241 }
242 bool done = currentRing->numPts() >= 3;
243 if (done) {
244 currentRing->init(*this);
245 }
246 *finalRing = currentRing;
247 return done;
248 }
249
250 // The general idea here is to, conceptually, start with the original polygon and slide
251 // the vertices along the bisectors until the first intersection. At that
252 // point two of the edges collapse and the process repeats on the new polygon.
253 // The polygon state is captured in the Ring class while the GrAAConvexTessellator
254 // controls the iteration. The CandidateVerts holds the formative points for the
255 // next ring.
tessellate(const SkMatrix & m,const SkPath & path)256 bool GrAAConvexTessellator::tessellate(const SkMatrix& m, const SkPath& path) {
257 if (!this->extractFromPath(m, path)) {
258 return false;
259 }
260
261 SkScalar coverage = 1.0f;
262 SkScalar scaleFactor = 0.0f;
263
264 if (SkStrokeRec::kStrokeAndFill_Style == fStyle) {
265 SkASSERT(m.isSimilarity());
266 scaleFactor = m.getMaxScale(); // x and y scale are the same
267 SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth;
268 Ring outerStrokeAndAARing;
269 this->createOuterRing(fInitialRing,
270 effectiveStrokeWidth / 2 + kAntialiasingRadius, 0.0,
271 &outerStrokeAndAARing);
272
273 // discard all the triangles added between the originating ring and the new outer ring
274 fIndices.rewind();
275
276 outerStrokeAndAARing.init(*this);
277
278 outerStrokeAndAARing.makeOriginalRing();
279
280 // Add the outer stroke ring's normals to the originating ring's normals
281 // so it can also act as an originating ring
282 fNorms.setCount(fNorms.count() + outerStrokeAndAARing.numPts());
283 for (int i = 0; i < outerStrokeAndAARing.numPts(); ++i) {
284 SkASSERT(outerStrokeAndAARing.index(i) < fNorms.count());
285 fNorms[outerStrokeAndAARing.index(i)] = outerStrokeAndAARing.norm(i);
286 }
287
288 // the bisectors are only needed for the computation of the outer ring
289 fBisectors.rewind();
290
291 Ring* insetAARing;
292 this->createInsetRings(outerStrokeAndAARing,
293 0.0f, 0.0f, 2*kAntialiasingRadius, 1.0f,
294 &insetAARing);
295
296 SkDEBUGCODE(this->validate();)
297 return true;
298 }
299
300 if (SkStrokeRec::kStroke_Style == fStyle) {
301 SkASSERT(fStrokeWidth >= 0.0f);
302 SkASSERT(m.isSimilarity());
303 scaleFactor = m.getMaxScale(); // x and y scale are the same
304 SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth;
305 Ring outerStrokeRing;
306 this->createOuterRing(fInitialRing, effectiveStrokeWidth / 2 - kAntialiasingRadius,
307 coverage, &outerStrokeRing);
308 outerStrokeRing.init(*this);
309 Ring outerAARing;
310 this->createOuterRing(outerStrokeRing, kAntialiasingRadius * 2, 0.0f, &outerAARing);
311 } else {
312 Ring outerAARing;
313 this->createOuterRing(fInitialRing, kAntialiasingRadius, 0.0f, &outerAARing);
314 }
315
316 // the bisectors are only needed for the computation of the outer ring
317 fBisectors.rewind();
318 if (SkStrokeRec::kStroke_Style == fStyle && fInitialRing.numPts() > 2) {
319 SkASSERT(fStrokeWidth >= 0.0f);
320 SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth;
321 Ring* insetStrokeRing;
322 SkScalar strokeDepth = effectiveStrokeWidth / 2 - kAntialiasingRadius;
323 if (this->createInsetRings(fInitialRing, 0.0f, coverage, strokeDepth, coverage,
324 &insetStrokeRing)) {
325 Ring* insetAARing;
326 this->createInsetRings(*insetStrokeRing, strokeDepth, coverage, strokeDepth +
327 kAntialiasingRadius * 2, 0.0f, &insetAARing);
328 }
329 } else {
330 Ring* insetAARing;
331 this->createInsetRings(fInitialRing, 0.0f, 0.5f, kAntialiasingRadius, 1.0f, &insetAARing);
332 }
333
334 SkDEBUGCODE(this->validate();)
335 return true;
336 }
337
computeDepthFromEdge(int edgeIdx,const SkPoint & p) const338 SkScalar GrAAConvexTessellator::computeDepthFromEdge(int edgeIdx, const SkPoint& p) const {
339 SkASSERT(edgeIdx < fNorms.count());
340
341 SkPoint v = p - fPts[edgeIdx];
342 SkScalar depth = -fNorms[edgeIdx].dot(v);
343 return depth;
344 }
345
346 // Find a point that is 'desiredDepth' away from the 'edgeIdx'-th edge and lies
347 // along the 'bisector' from the 'startIdx'-th point.
computePtAlongBisector(int startIdx,const SkVector & bisector,int edgeIdx,SkScalar desiredDepth,SkPoint * result) const348 bool GrAAConvexTessellator::computePtAlongBisector(int startIdx,
349 const SkVector& bisector,
350 int edgeIdx,
351 SkScalar desiredDepth,
352 SkPoint* result) const {
353 const SkPoint& norm = fNorms[edgeIdx];
354
355 // First find the point where the edge and the bisector intersect
356 SkPoint newP;
357
358 SkScalar t = perp_intersect(fPts[startIdx], bisector, fPts[edgeIdx], norm);
359 if (SkScalarNearlyEqual(t, 0.0f)) {
360 // the start point was one of the original ring points
361 SkASSERT(startIdx < fPts.count());
362 newP = fPts[startIdx];
363 } else if (t < 0.0f) {
364 newP = bisector;
365 newP.scale(t);
366 newP += fPts[startIdx];
367 } else {
368 return false;
369 }
370
371 // Then offset along the bisector from that point the correct distance
372 SkScalar dot = bisector.dot(norm);
373 t = -desiredDepth / dot;
374 *result = bisector;
375 result->scale(t);
376 *result += newP;
377
378 return true;
379 }
380
extractFromPath(const SkMatrix & m,const SkPath & path)381 bool GrAAConvexTessellator::extractFromPath(const SkMatrix& m, const SkPath& path) {
382 SkASSERT(path.isConvex());
383
384 SkRect bounds = path.getBounds();
385 m.mapRect(&bounds);
386 if (!bounds.isFinite()) {
387 // We could do something smarter here like clip the path based on the bounds of the dst.
388 // We'd have to be careful about strokes to ensure we don't draw something wrong.
389 return false;
390 }
391
392 // Outer ring: 3*numPts
393 // Middle ring: numPts
394 // Presumptive inner ring: numPts
395 this->reservePts(5*path.countPoints());
396 // Outer ring: 12*numPts
397 // Middle ring: 0
398 // Presumptive inner ring: 6*numPts + 6
399 fIndices.setReserve(18*path.countPoints() + 6);
400
401 // Reset the accumulated error for all the future lineTo() calls when iterating over the path.
402 fAccumLinearError = 0.f;
403 // TODO: is there a faster way to extract the points from the path? Perhaps
404 // get all the points via a new entry point, transform them all in bulk
405 // and then walk them to find duplicates?
406 SkPathEdgeIter iter(path);
407 while (auto e = iter.next()) {
408 switch (e.fEdge) {
409 case SkPathEdgeIter::Edge::kLine:
410 if (!SkPathPriv::AllPointsEq(e.fPts, 2)) {
411 this->lineTo(m, e.fPts[1], kSharp_CurveState);
412 }
413 break;
414 case SkPathEdgeIter::Edge::kQuad:
415 if (!SkPathPriv::AllPointsEq(e.fPts, 3)) {
416 this->quadTo(m, e.fPts);
417 }
418 break;
419 case SkPathEdgeIter::Edge::kCubic:
420 if (!SkPathPriv::AllPointsEq(e.fPts, 4)) {
421 this->cubicTo(m, e.fPts);
422 }
423 break;
424 case SkPathEdgeIter::Edge::kConic:
425 if (!SkPathPriv::AllPointsEq(e.fPts, 3)) {
426 this->conicTo(m, e.fPts, iter.conicWeight());
427 }
428 break;
429 }
430 }
431
432 if (this->numPts() < 2) {
433 return false;
434 }
435
436 // check if last point is a duplicate of the first point. If so, remove it.
437 if (duplicate_pt(fPts[this->numPts()-1], fPts[0])) {
438 this->popLastPt();
439 }
440
441 // Remove any lingering colinear points where the path wraps around
442 fAccumLinearError = 0.f;
443 bool noRemovalsToDo = false;
444 while (!noRemovalsToDo && this->numPts() >= 3) {
445 if (points_are_colinear_and_b_is_middle(fPts[fPts.count() - 2], fPts.top(), fPts[0],
446 &fAccumLinearError)) {
447 this->popLastPt();
448 } else if (points_are_colinear_and_b_is_middle(fPts.top(), fPts[0], fPts[1],
449 &fAccumLinearError)) {
450 this->popFirstPtShuffle();
451 } else {
452 noRemovalsToDo = true;
453 }
454 }
455
456 // Compute the normals and bisectors.
457 SkASSERT(fNorms.empty());
458 if (this->numPts() >= 3) {
459 this->computeNormals();
460 this->computeBisectors();
461 } else if (this->numPts() == 2) {
462 // We've got two points, so we're degenerate.
463 if (fStyle == SkStrokeRec::kFill_Style) {
464 // it's a fill, so we don't need to worry about degenerate paths
465 return false;
466 }
467 // For stroking, we still need to process the degenerate path, so fix it up
468 fSide = SkPointPriv::kLeft_Side;
469
470 fNorms.append(2);
471 fNorms[0] = SkPointPriv::MakeOrthog(fPts[1] - fPts[0], fSide);
472 fNorms[0].normalize();
473 fNorms[1] = -fNorms[0];
474 SkASSERT(SkScalarNearlyEqual(1.0f, fNorms[0].length()));
475 // we won't actually use the bisectors, so just push zeroes
476 fBisectors.push_back(SkPoint::Make(0.0, 0.0));
477 fBisectors.push_back(SkPoint::Make(0.0, 0.0));
478 } else {
479 return false;
480 }
481
482 fCandidateVerts.setReserve(this->numPts());
483 fInitialRing.setReserve(this->numPts());
484 for (int i = 0; i < this->numPts(); ++i) {
485 fInitialRing.addIdx(i, i);
486 }
487 fInitialRing.init(fNorms, fBisectors);
488
489 this->validate();
490 return true;
491 }
492
getNextRing(Ring * lastRing)493 GrAAConvexTessellator::Ring* GrAAConvexTessellator::getNextRing(Ring* lastRing) {
494 #if GR_AA_CONVEX_TESSELLATOR_VIZ
495 Ring* ring = *fRings.push() = new Ring;
496 ring->setReserve(fInitialRing.numPts());
497 ring->rewind();
498 return ring;
499 #else
500 // Flip flop back and forth between fRings[0] & fRings[1]
501 int nextRing = (lastRing == &fRings[0]) ? 1 : 0;
502 fRings[nextRing].setReserve(fInitialRing.numPts());
503 fRings[nextRing].rewind();
504 return &fRings[nextRing];
505 #endif
506 }
507
fanRing(const Ring & ring)508 void GrAAConvexTessellator::fanRing(const Ring& ring) {
509 // fan out from point 0
510 int startIdx = ring.index(0);
511 for (int cur = ring.numPts() - 2; cur >= 0; --cur) {
512 this->addTri(startIdx, ring.index(cur), ring.index(cur + 1));
513 }
514 }
515
createOuterRing(const Ring & previousRing,SkScalar outset,SkScalar coverage,Ring * nextRing)516 void GrAAConvexTessellator::createOuterRing(const Ring& previousRing, SkScalar outset,
517 SkScalar coverage, Ring* nextRing) {
518 const int numPts = previousRing.numPts();
519 if (numPts == 0) {
520 return;
521 }
522
523 int prev = numPts - 1;
524 int lastPerpIdx = -1, firstPerpIdx = -1;
525
526 const SkScalar outsetSq = outset * outset;
527 SkScalar miterLimitSq = outset * fMiterLimit;
528 miterLimitSq = miterLimitSq * miterLimitSq;
529 for (int cur = 0; cur < numPts; ++cur) {
530 int originalIdx = previousRing.index(cur);
531 // For each vertex of the original polygon we add at least two points to the
532 // outset polygon - one extending perpendicular to each impinging edge. Connecting these
533 // two points yields a bevel join. We need one additional point for a mitered join, and
534 // a round join requires one or more points depending upon curvature.
535
536 // The perpendicular point for the last edge
537 SkPoint normal1 = previousRing.norm(prev);
538 SkPoint perp1 = normal1;
539 perp1.scale(outset);
540 perp1 += this->point(originalIdx);
541
542 // The perpendicular point for the next edge.
543 SkPoint normal2 = previousRing.norm(cur);
544 SkPoint perp2 = normal2;
545 perp2.scale(outset);
546 perp2 += fPts[originalIdx];
547
548 CurveState curve = fCurveState[originalIdx];
549
550 // We know it isn't a duplicate of the prior point (since it and this
551 // one are just perpendicular offsets from the non-merged polygon points)
552 int perp1Idx = this->addPt(perp1, -outset, coverage, false, curve);
553 nextRing->addIdx(perp1Idx, originalIdx);
554
555 int perp2Idx;
556 // For very shallow angles all the corner points could fuse.
557 if (duplicate_pt(perp2, this->point(perp1Idx))) {
558 perp2Idx = perp1Idx;
559 } else {
560 perp2Idx = this->addPt(perp2, -outset, coverage, false, curve);
561 }
562
563 if (perp2Idx != perp1Idx) {
564 if (curve == kCurve_CurveState) {
565 // bevel or round depending upon curvature
566 SkScalar dotProd = normal1.dot(normal2);
567 if (dotProd < kRoundCapThreshold) {
568 // Currently we "round" by creating a single extra point, which produces
569 // good results for common cases. For thick strokes with high curvature, we will
570 // need to add more points; for the time being we simply fall back to software
571 // rendering for thick strokes.
572 SkPoint miter = previousRing.bisector(cur);
573 miter.setLength(-outset);
574 miter += fPts[originalIdx];
575
576 // For very shallow angles all the corner points could fuse
577 if (!duplicate_pt(miter, this->point(perp1Idx))) {
578 int miterIdx;
579 miterIdx = this->addPt(miter, -outset, coverage, false, kSharp_CurveState);
580 nextRing->addIdx(miterIdx, originalIdx);
581 // The two triangles for the corner
582 this->addTri(originalIdx, perp1Idx, miterIdx);
583 this->addTri(originalIdx, miterIdx, perp2Idx);
584 }
585 } else {
586 this->addTri(originalIdx, perp1Idx, perp2Idx);
587 }
588 } else {
589 switch (fJoin) {
590 case SkPaint::Join::kMiter_Join: {
591 // The bisector outset point
592 SkPoint miter = previousRing.bisector(cur);
593 SkScalar dotProd = normal1.dot(normal2);
594 // The max is because this could go slightly negative if precision causes
595 // us to become slightly concave.
596 SkScalar sinHalfAngleSq = std::max(SkScalarHalf(SK_Scalar1 + dotProd), 0.f);
597 SkScalar lengthSq = sk_ieee_float_divide(outsetSq, sinHalfAngleSq);
598 if (lengthSq > miterLimitSq) {
599 // just bevel it
600 this->addTri(originalIdx, perp1Idx, perp2Idx);
601 break;
602 }
603 miter.setLength(-SkScalarSqrt(lengthSq));
604 miter += fPts[originalIdx];
605
606 // For very shallow angles all the corner points could fuse
607 if (!duplicate_pt(miter, this->point(perp1Idx))) {
608 int miterIdx;
609 miterIdx = this->addPt(miter, -outset, coverage, false,
610 kSharp_CurveState);
611 nextRing->addIdx(miterIdx, originalIdx);
612 // The two triangles for the corner
613 this->addTri(originalIdx, perp1Idx, miterIdx);
614 this->addTri(originalIdx, miterIdx, perp2Idx);
615 } else {
616 // ignore the miter point as it's so close to perp1/perp2 and simply
617 // bevel.
618 this->addTri(originalIdx, perp1Idx, perp2Idx);
619 }
620 break;
621 }
622 case SkPaint::Join::kBevel_Join:
623 this->addTri(originalIdx, perp1Idx, perp2Idx);
624 break;
625 default:
626 // kRound_Join is unsupported for now. AALinearizingConvexPathRenderer is
627 // only willing to draw mitered or beveled, so we should never get here.
628 SkASSERT(false);
629 }
630 }
631
632 nextRing->addIdx(perp2Idx, originalIdx);
633 }
634
635 if (0 == cur) {
636 // Store the index of the first perpendicular point to finish up
637 firstPerpIdx = perp1Idx;
638 SkASSERT(-1 == lastPerpIdx);
639 } else {
640 // The triangles for the previous edge
641 int prevIdx = previousRing.index(prev);
642 this->addTri(prevIdx, perp1Idx, originalIdx);
643 this->addTri(prevIdx, lastPerpIdx, perp1Idx);
644 }
645
646 // Track the last perpendicular outset point so we can construct the
647 // trailing edge triangles.
648 lastPerpIdx = perp2Idx;
649 prev = cur;
650 }
651
652 // pick up the final edge rect
653 int lastIdx = previousRing.index(numPts - 1);
654 this->addTri(lastIdx, firstPerpIdx, previousRing.index(0));
655 this->addTri(lastIdx, lastPerpIdx, firstPerpIdx);
656
657 this->validate();
658 }
659
660 // Something went wrong in the creation of the next ring. If we're filling the shape, just go ahead
661 // and fan it.
terminate(const Ring & ring)662 void GrAAConvexTessellator::terminate(const Ring& ring) {
663 if (fStyle != SkStrokeRec::kStroke_Style && ring.numPts() > 0) {
664 this->fanRing(ring);
665 }
666 }
667
compute_coverage(SkScalar depth,SkScalar initialDepth,SkScalar initialCoverage,SkScalar targetDepth,SkScalar targetCoverage)668 static SkScalar compute_coverage(SkScalar depth, SkScalar initialDepth, SkScalar initialCoverage,
669 SkScalar targetDepth, SkScalar targetCoverage) {
670 if (SkScalarNearlyEqual(initialDepth, targetDepth)) {
671 return targetCoverage;
672 }
673 SkScalar result = (depth - initialDepth) / (targetDepth - initialDepth) *
674 (targetCoverage - initialCoverage) + initialCoverage;
675 return SkTPin(result, 0.0f, 1.0f);
676 }
677
678 // return true when processing is complete
createInsetRing(const Ring & lastRing,Ring * nextRing,SkScalar initialDepth,SkScalar initialCoverage,SkScalar targetDepth,SkScalar targetCoverage,bool forceNew)679 bool GrAAConvexTessellator::createInsetRing(const Ring& lastRing, Ring* nextRing,
680 SkScalar initialDepth, SkScalar initialCoverage,
681 SkScalar targetDepth, SkScalar targetCoverage,
682 bool forceNew) {
683 bool done = false;
684
685 fCandidateVerts.rewind();
686
687 // Loop through all the points in the ring and find the intersection with the smallest depth
688 SkScalar minDist = SK_ScalarMax, minT = 0.0f;
689 int minEdgeIdx = -1;
690
691 for (int cur = 0; cur < lastRing.numPts(); ++cur) {
692 int next = (cur + 1) % lastRing.numPts();
693
694 SkScalar t;
695 bool result = intersect(this->point(lastRing.index(cur)), lastRing.bisector(cur),
696 this->point(lastRing.index(next)), lastRing.bisector(next),
697 &t);
698 // The bisectors may be parallel (!result) or the previous ring may have become slightly
699 // concave due to accumulated error (t <= 0).
700 if (!result || t <= 0) {
701 continue;
702 }
703 SkScalar dist = -t * lastRing.norm(cur).dot(lastRing.bisector(cur));
704
705 if (minDist > dist) {
706 minDist = dist;
707 minT = t;
708 minEdgeIdx = cur;
709 }
710 }
711
712 if (minEdgeIdx == -1) {
713 return false;
714 }
715 SkPoint newPt = lastRing.bisector(minEdgeIdx);
716 newPt.scale(minT);
717 newPt += this->point(lastRing.index(minEdgeIdx));
718
719 SkScalar depth = this->computeDepthFromEdge(lastRing.origEdgeID(minEdgeIdx), newPt);
720 if (depth >= targetDepth) {
721 // None of the bisectors intersect before reaching the desired depth.
722 // Just step them all to the desired depth
723 depth = targetDepth;
724 done = true;
725 }
726
727 // 'dst' stores where each point in the last ring maps to/transforms into
728 // in the next ring.
729 SkTDArray<int> dst;
730 dst.setCount(lastRing.numPts());
731
732 // Create the first point (who compares with no one)
733 if (!this->computePtAlongBisector(lastRing.index(0),
734 lastRing.bisector(0),
735 lastRing.origEdgeID(0),
736 depth, &newPt)) {
737 this->terminate(lastRing);
738 return true;
739 }
740 dst[0] = fCandidateVerts.addNewPt(newPt,
741 lastRing.index(0), lastRing.origEdgeID(0),
742 !this->movable(lastRing.index(0)));
743
744 // Handle the middle points (who only compare with the prior point)
745 for (int cur = 1; cur < lastRing.numPts()-1; ++cur) {
746 if (!this->computePtAlongBisector(lastRing.index(cur),
747 lastRing.bisector(cur),
748 lastRing.origEdgeID(cur),
749 depth, &newPt)) {
750 this->terminate(lastRing);
751 return true;
752 }
753 if (!duplicate_pt(newPt, fCandidateVerts.lastPoint())) {
754 dst[cur] = fCandidateVerts.addNewPt(newPt,
755 lastRing.index(cur), lastRing.origEdgeID(cur),
756 !this->movable(lastRing.index(cur)));
757 } else {
758 dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur));
759 }
760 }
761
762 // Check on the last point (handling the wrap around)
763 int cur = lastRing.numPts()-1;
764 if (!this->computePtAlongBisector(lastRing.index(cur),
765 lastRing.bisector(cur),
766 lastRing.origEdgeID(cur),
767 depth, &newPt)) {
768 this->terminate(lastRing);
769 return true;
770 }
771 bool dupPrev = duplicate_pt(newPt, fCandidateVerts.lastPoint());
772 bool dupNext = duplicate_pt(newPt, fCandidateVerts.firstPoint());
773
774 if (!dupPrev && !dupNext) {
775 dst[cur] = fCandidateVerts.addNewPt(newPt,
776 lastRing.index(cur), lastRing.origEdgeID(cur),
777 !this->movable(lastRing.index(cur)));
778 } else if (dupPrev && !dupNext) {
779 dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur));
780 } else if (!dupPrev && dupNext) {
781 dst[cur] = fCandidateVerts.fuseWithNext();
782 } else {
783 bool dupPrevVsNext = duplicate_pt(fCandidateVerts.firstPoint(), fCandidateVerts.lastPoint());
784
785 if (!dupPrevVsNext) {
786 dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur));
787 } else {
788 const int fused = fCandidateVerts.fuseWithBoth();
789 dst[cur] = fused;
790 const int targetIdx = dst[cur - 1];
791 for (int i = cur - 1; i >= 0 && dst[i] == targetIdx; i--) {
792 dst[i] = fused;
793 }
794 }
795 }
796
797 // Fold the new ring's points into the global pool
798 for (int i = 0; i < fCandidateVerts.numPts(); ++i) {
799 int newIdx;
800 if (fCandidateVerts.needsToBeNew(i) || forceNew) {
801 // if the originating index is still valid then this point wasn't
802 // fused (and is thus movable)
803 SkScalar coverage = compute_coverage(depth, initialDepth, initialCoverage,
804 targetDepth, targetCoverage);
805 newIdx = this->addPt(fCandidateVerts.point(i), depth, coverage,
806 fCandidateVerts.originatingIdx(i) != -1, kSharp_CurveState);
807 } else {
808 SkASSERT(fCandidateVerts.originatingIdx(i) != -1);
809 this->updatePt(fCandidateVerts.originatingIdx(i), fCandidateVerts.point(i), depth,
810 targetCoverage);
811 newIdx = fCandidateVerts.originatingIdx(i);
812 }
813
814 nextRing->addIdx(newIdx, fCandidateVerts.origEdge(i));
815 }
816
817 // 'dst' currently has indices into the ring. Remap these to be indices
818 // into the global pool since the triangulation operates in that space.
819 for (int i = 0; i < dst.count(); ++i) {
820 dst[i] = nextRing->index(dst[i]);
821 }
822
823 for (int i = 0; i < lastRing.numPts(); ++i) {
824 int next = (i + 1) % lastRing.numPts();
825
826 this->addTri(lastRing.index(i), lastRing.index(next), dst[next]);
827 this->addTri(lastRing.index(i), dst[next], dst[i]);
828 }
829
830 if (done && fStyle != SkStrokeRec::kStroke_Style) {
831 // fill or stroke-and-fill
832 this->fanRing(*nextRing);
833 }
834
835 if (nextRing->numPts() < 3) {
836 done = true;
837 }
838 return done;
839 }
840
validate() const841 void GrAAConvexTessellator::validate() const {
842 SkASSERT(fPts.count() == fMovable.count());
843 SkASSERT(fPts.count() == fCoverages.count());
844 SkASSERT(fPts.count() == fCurveState.count());
845 SkASSERT(0 == (fIndices.count() % 3));
846 SkASSERT(!fBisectors.count() || fBisectors.count() == fNorms.count());
847 }
848
849 //////////////////////////////////////////////////////////////////////////////
init(const GrAAConvexTessellator & tess)850 void GrAAConvexTessellator::Ring::init(const GrAAConvexTessellator& tess) {
851 this->computeNormals(tess);
852 this->computeBisectors(tess);
853 }
854
init(const SkTDArray<SkVector> & norms,const SkTDArray<SkVector> & bisectors)855 void GrAAConvexTessellator::Ring::init(const SkTDArray<SkVector>& norms,
856 const SkTDArray<SkVector>& bisectors) {
857 for (int i = 0; i < fPts.count(); ++i) {
858 fPts[i].fNorm = norms[i];
859 fPts[i].fBisector = bisectors[i];
860 }
861 }
862
863 // Compute the outward facing normal at each vertex.
computeNormals(const GrAAConvexTessellator & tess)864 void GrAAConvexTessellator::Ring::computeNormals(const GrAAConvexTessellator& tess) {
865 for (int cur = 0; cur < fPts.count(); ++cur) {
866 int next = (cur + 1) % fPts.count();
867
868 fPts[cur].fNorm = tess.point(fPts[next].fIndex) - tess.point(fPts[cur].fIndex);
869 SkPoint::Normalize(&fPts[cur].fNorm);
870 fPts[cur].fNorm = SkPointPriv::MakeOrthog(fPts[cur].fNorm, tess.side());
871 }
872 }
873
computeBisectors(const GrAAConvexTessellator & tess)874 void GrAAConvexTessellator::Ring::computeBisectors(const GrAAConvexTessellator& tess) {
875 int prev = fPts.count() - 1;
876 for (int cur = 0; cur < fPts.count(); prev = cur, ++cur) {
877 fPts[cur].fBisector = fPts[cur].fNorm + fPts[prev].fNorm;
878 if (!fPts[cur].fBisector.normalize()) {
879 fPts[cur].fBisector =
880 SkPointPriv::MakeOrthog(fPts[cur].fNorm, (SkPointPriv::Side)-tess.side()) +
881 SkPointPriv::MakeOrthog(fPts[prev].fNorm, tess.side());
882 SkAssertResult(fPts[cur].fBisector.normalize());
883 } else {
884 fPts[cur].fBisector.negate(); // make the bisector face in
885 }
886 }
887 }
888
889 //////////////////////////////////////////////////////////////////////////////
890 #ifdef SK_DEBUG
891 // Is this ring convex?
isConvex(const GrAAConvexTessellator & tess) const892 bool GrAAConvexTessellator::Ring::isConvex(const GrAAConvexTessellator& tess) const {
893 if (fPts.count() < 3) {
894 return true;
895 }
896
897 SkPoint prev = tess.point(fPts[0].fIndex) - tess.point(fPts.top().fIndex);
898 SkPoint cur = tess.point(fPts[1].fIndex) - tess.point(fPts[0].fIndex);
899 SkScalar minDot = prev.fX * cur.fY - prev.fY * cur.fX;
900 SkScalar maxDot = minDot;
901
902 prev = cur;
903 for (int i = 1; i < fPts.count(); ++i) {
904 int next = (i + 1) % fPts.count();
905
906 cur = tess.point(fPts[next].fIndex) - tess.point(fPts[i].fIndex);
907 SkScalar dot = prev.fX * cur.fY - prev.fY * cur.fX;
908
909 minDot = std::min(minDot, dot);
910 maxDot = std::max(maxDot, dot);
911
912 prev = cur;
913 }
914
915 if (SkScalarNearlyEqual(maxDot, 0.0f, 0.005f)) {
916 maxDot = 0;
917 }
918 if (SkScalarNearlyEqual(minDot, 0.0f, 0.005f)) {
919 minDot = 0;
920 }
921 return (maxDot >= 0.0f) == (minDot >= 0.0f);
922 }
923
924 #endif
925
lineTo(const SkPoint & p,CurveState curve)926 void GrAAConvexTessellator::lineTo(const SkPoint& p, CurveState curve) {
927 if (this->numPts() > 0 && duplicate_pt(p, this->lastPoint())) {
928 return;
929 }
930
931 if (this->numPts() >= 2 &&
932 points_are_colinear_and_b_is_middle(fPts[fPts.count() - 2], fPts.top(), p,
933 &fAccumLinearError)) {
934 // The old last point is on the line from the second to last to the new point
935 this->popLastPt();
936 // double-check that the new last point is not a duplicate of the new point. In an ideal
937 // world this wouldn't be necessary (since it's only possible for non-convex paths), but
938 // floating point precision issues mean it can actually happen on paths that were
939 // determined to be convex.
940 if (duplicate_pt(p, this->lastPoint())) {
941 return;
942 }
943 } else {
944 fAccumLinearError = 0.f;
945 }
946 SkScalar initialRingCoverage = (SkStrokeRec::kFill_Style == fStyle) ? 0.5f : 1.0f;
947 this->addPt(p, 0.0f, initialRingCoverage, false, curve);
948 }
949
lineTo(const SkMatrix & m,const SkPoint & p,CurveState curve)950 void GrAAConvexTessellator::lineTo(const SkMatrix& m, const SkPoint& p, CurveState curve) {
951 this->lineTo(m.mapXY(p.fX, p.fY), curve);
952 }
953
quadTo(const SkPoint pts[3])954 void GrAAConvexTessellator::quadTo(const SkPoint pts[3]) {
955 int maxCount = GrPathUtils::quadraticPointCount(pts, kQuadTolerance);
956 fPointBuffer.setCount(maxCount);
957 SkPoint* target = fPointBuffer.begin();
958 int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2],
959 kQuadTolerance, &target, maxCount);
960 fPointBuffer.setCount(count);
961 for (int i = 0; i < count - 1; i++) {
962 this->lineTo(fPointBuffer[i], kCurve_CurveState);
963 }
964 this->lineTo(fPointBuffer[count - 1], kIndeterminate_CurveState);
965 }
966
quadTo(const SkMatrix & m,const SkPoint srcPts[3])967 void GrAAConvexTessellator::quadTo(const SkMatrix& m, const SkPoint srcPts[3]) {
968 SkPoint pts[3];
969 m.mapPoints(pts, srcPts, 3);
970 this->quadTo(pts);
971 }
972
cubicTo(const SkMatrix & m,const SkPoint srcPts[4])973 void GrAAConvexTessellator::cubicTo(const SkMatrix& m, const SkPoint srcPts[4]) {
974 SkPoint pts[4];
975 m.mapPoints(pts, srcPts, 4);
976 int maxCount = GrPathUtils::cubicPointCount(pts, kCubicTolerance);
977 fPointBuffer.setCount(maxCount);
978 SkPoint* target = fPointBuffer.begin();
979 int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3],
980 kCubicTolerance, &target, maxCount);
981 fPointBuffer.setCount(count);
982 for (int i = 0; i < count - 1; i++) {
983 this->lineTo(fPointBuffer[i], kCurve_CurveState);
984 }
985 this->lineTo(fPointBuffer[count - 1], kIndeterminate_CurveState);
986 }
987
988 // include down here to avoid compilation errors caused by "-" overload in SkGeometry.h
989 #include "src/core/SkGeometry.h"
990
conicTo(const SkMatrix & m,const SkPoint srcPts[3],SkScalar w)991 void GrAAConvexTessellator::conicTo(const SkMatrix& m, const SkPoint srcPts[3], SkScalar w) {
992 SkPoint pts[3];
993 m.mapPoints(pts, srcPts, 3);
994 SkAutoConicToQuads quadder;
995 const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
996 SkPoint lastPoint = *(quads++);
997 int count = quadder.countQuads();
998 for (int i = 0; i < count; ++i) {
999 SkPoint quadPts[3];
1000 quadPts[0] = lastPoint;
1001 quadPts[1] = quads[0];
1002 quadPts[2] = i == count - 1 ? pts[2] : quads[1];
1003 this->quadTo(quadPts);
1004 lastPoint = quadPts[2];
1005 quads += 2;
1006 }
1007 }
1008
1009 //////////////////////////////////////////////////////////////////////////////
1010 #if GR_AA_CONVEX_TESSELLATOR_VIZ
1011 static const SkScalar kPointRadius = 0.02f;
1012 static const SkScalar kArrowStrokeWidth = 0.0f;
1013 static const SkScalar kArrowLength = 0.2f;
1014 static const SkScalar kEdgeTextSize = 0.1f;
1015 static const SkScalar kPointTextSize = 0.02f;
1016
draw_point(SkCanvas * canvas,const SkPoint & p,SkScalar paramValue,bool stroke)1017 static void draw_point(SkCanvas* canvas, const SkPoint& p, SkScalar paramValue, bool stroke) {
1018 SkPaint paint;
1019 SkASSERT(paramValue <= 1.0f);
1020 int gs = int(255*paramValue);
1021 paint.setARGB(255, gs, gs, gs);
1022
1023 canvas->drawCircle(p.fX, p.fY, kPointRadius, paint);
1024
1025 if (stroke) {
1026 SkPaint stroke;
1027 stroke.setColor(SK_ColorYELLOW);
1028 stroke.setStyle(SkPaint::kStroke_Style);
1029 stroke.setStrokeWidth(kPointRadius/3.0f);
1030 canvas->drawCircle(p.fX, p.fY, kPointRadius, stroke);
1031 }
1032 }
1033
draw_line(SkCanvas * canvas,const SkPoint & p0,const SkPoint & p1,SkColor color)1034 static void draw_line(SkCanvas* canvas, const SkPoint& p0, const SkPoint& p1, SkColor color) {
1035 SkPaint p;
1036 p.setColor(color);
1037
1038 canvas->drawLine(p0.fX, p0.fY, p1.fX, p1.fY, p);
1039 }
1040
draw_arrow(SkCanvas * canvas,const SkPoint & p,const SkPoint & n,SkScalar len,SkColor color)1041 static void draw_arrow(SkCanvas*canvas, const SkPoint& p, const SkPoint &n,
1042 SkScalar len, SkColor color) {
1043 SkPaint paint;
1044 paint.setColor(color);
1045 paint.setStrokeWidth(kArrowStrokeWidth);
1046 paint.setStyle(SkPaint::kStroke_Style);
1047
1048 canvas->drawLine(p.fX, p.fY,
1049 p.fX + len * n.fX, p.fY + len * n.fY,
1050 paint);
1051 }
1052
draw(SkCanvas * canvas,const GrAAConvexTessellator & tess) const1053 void GrAAConvexTessellator::Ring::draw(SkCanvas* canvas, const GrAAConvexTessellator& tess) const {
1054 SkPaint paint;
1055 paint.setTextSize(kEdgeTextSize);
1056
1057 for (int cur = 0; cur < fPts.count(); ++cur) {
1058 int next = (cur + 1) % fPts.count();
1059
1060 draw_line(canvas,
1061 tess.point(fPts[cur].fIndex),
1062 tess.point(fPts[next].fIndex),
1063 SK_ColorGREEN);
1064
1065 SkPoint mid = tess.point(fPts[cur].fIndex) + tess.point(fPts[next].fIndex);
1066 mid.scale(0.5f);
1067
1068 if (fPts.count()) {
1069 draw_arrow(canvas, mid, fPts[cur].fNorm, kArrowLength, SK_ColorRED);
1070 mid.fX += (kArrowLength/2) * fPts[cur].fNorm.fX;
1071 mid.fY += (kArrowLength/2) * fPts[cur].fNorm.fY;
1072 }
1073
1074 SkString num;
1075 num.printf("%d", this->origEdgeID(cur));
1076 canvas->drawString(num, mid.fX, mid.fY, paint);
1077
1078 if (fPts.count()) {
1079 draw_arrow(canvas, tess.point(fPts[cur].fIndex), fPts[cur].fBisector,
1080 kArrowLength, SK_ColorBLUE);
1081 }
1082 }
1083 }
1084
draw(SkCanvas * canvas) const1085 void GrAAConvexTessellator::draw(SkCanvas* canvas) const {
1086 for (int i = 0; i < fIndices.count(); i += 3) {
1087 SkASSERT(fIndices[i] < this->numPts()) ;
1088 SkASSERT(fIndices[i+1] < this->numPts()) ;
1089 SkASSERT(fIndices[i+2] < this->numPts()) ;
1090
1091 draw_line(canvas,
1092 this->point(this->fIndices[i]), this->point(this->fIndices[i+1]),
1093 SK_ColorBLACK);
1094 draw_line(canvas,
1095 this->point(this->fIndices[i+1]), this->point(this->fIndices[i+2]),
1096 SK_ColorBLACK);
1097 draw_line(canvas,
1098 this->point(this->fIndices[i+2]), this->point(this->fIndices[i]),
1099 SK_ColorBLACK);
1100 }
1101
1102 fInitialRing.draw(canvas, *this);
1103 for (int i = 0; i < fRings.count(); ++i) {
1104 fRings[i]->draw(canvas, *this);
1105 }
1106
1107 for (int i = 0; i < this->numPts(); ++i) {
1108 draw_point(canvas,
1109 this->point(i), 0.5f + (this->depth(i)/(2 * kAntialiasingRadius)),
1110 !this->movable(i));
1111
1112 SkPaint paint;
1113 paint.setTextSize(kPointTextSize);
1114 if (this->depth(i) <= -kAntialiasingRadius) {
1115 paint.setColor(SK_ColorWHITE);
1116 }
1117
1118 SkString num;
1119 num.printf("%d", i);
1120 canvas->drawString(num,
1121 this->point(i).fX, this->point(i).fY+(kPointRadius/2.0f),
1122 paint);
1123 }
1124 }
1125
1126 #endif
1127