• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright 2009 The Android Open Source Project
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  
9  #include "SkEdgeClipper.h"
10  #include "SkGeometry.h"
11  #include "SkLineClipper.h"
12  
quick_reject(const SkRect & bounds,const SkRect & clip)13  static bool quick_reject(const SkRect& bounds, const SkRect& clip) {
14      return bounds.fTop >= clip.fBottom || bounds.fBottom <= clip.fTop;
15  }
16  
clamp_le(SkScalar & value,SkScalar max)17  static inline void clamp_le(SkScalar& value, SkScalar max) {
18      if (value > max) {
19          value = max;
20      }
21  }
22  
clamp_ge(SkScalar & value,SkScalar min)23  static inline void clamp_ge(SkScalar& value, SkScalar min) {
24      if (value < min) {
25          value = min;
26      }
27  }
28  
29  /*  src[] must be monotonic in Y. This routine copies src into dst, and sorts
30   it to be increasing in Y. If it had to reverse the order of the points,
31   it returns true, otherwise it returns false
32   */
sort_increasing_Y(SkPoint dst[],const SkPoint src[],int count)33  static bool sort_increasing_Y(SkPoint dst[], const SkPoint src[], int count) {
34      // we need the data to be monotonically increasing in Y
35      if (src[0].fY > src[count - 1].fY) {
36          for (int i = 0; i < count; i++) {
37              dst[i] = src[count - i - 1];
38          }
39          return true;
40      } else {
41          memcpy(dst, src, count * sizeof(SkPoint));
42          return false;
43      }
44  }
45  
clipLine(SkPoint p0,SkPoint p1,const SkRect & clip)46  bool SkEdgeClipper::clipLine(SkPoint p0, SkPoint p1, const SkRect& clip) {
47      fCurrPoint = fPoints;
48      fCurrVerb = fVerbs;
49  
50      SkPoint lines[SkLineClipper::kMaxPoints];
51      const SkPoint pts[] = { p0, p1 };
52      int lineCount = SkLineClipper::ClipLine(pts, clip, lines, fCanCullToTheRight);
53      for (int i = 0; i < lineCount; i++) {
54          this->appendLine(lines[i], lines[i + 1]);
55      }
56  
57      *fCurrVerb = SkPath::kDone_Verb;
58      fCurrPoint = fPoints;
59      fCurrVerb = fVerbs;
60      return SkPath::kDone_Verb != fVerbs[0];
61  }
62  
63  ///////////////////////////////////////////////////////////////////////////////
64  
chopMonoQuadAt(SkScalar c0,SkScalar c1,SkScalar c2,SkScalar target,SkScalar * t)65  static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2,
66                             SkScalar target, SkScalar* t) {
67      /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2
68       *  We solve for t, using quadratic equation, hence we have to rearrange
69       * our cooefficents to look like At^2 + Bt + C
70       */
71      SkScalar A = c0 - c1 - c1 + c2;
72      SkScalar B = 2*(c1 - c0);
73      SkScalar C = c0 - target;
74  
75      SkScalar roots[2];  // we only expect one, but make room for 2 for safety
76      int count = SkFindUnitQuadRoots(A, B, C, roots);
77      if (count) {
78          *t = roots[0];
79          return true;
80      }
81      return false;
82  }
83  
chopMonoQuadAtY(SkPoint pts[3],SkScalar y,SkScalar * t)84  static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
85      return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t);
86  }
87  
chopMonoQuadAtX(SkPoint pts[3],SkScalar x,SkScalar * t)88  static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) {
89      return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t);
90  }
91  
92  // Modify pts[] in place so that it is clipped in Y to the clip rect
chop_quad_in_Y(SkPoint pts[3],const SkRect & clip)93  static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) {
94      SkScalar t;
95      SkPoint tmp[5]; // for SkChopQuadAt
96  
97      // are we partially above
98      if (pts[0].fY < clip.fTop) {
99          if (chopMonoQuadAtY(pts, clip.fTop, &t)) {
100              // take the 2nd chopped quad
101              SkChopQuadAt(pts, tmp, t);
102              // clamp to clean up imprecise numerics in the chop
103              tmp[2].fY = clip.fTop;
104              clamp_ge(tmp[3].fY, clip.fTop);
105  
106              pts[0] = tmp[2];
107              pts[1] = tmp[3];
108          } else {
109              // if chopMonoQuadAtY failed, then we may have hit inexact numerics
110              // so we just clamp against the top
111              for (int i = 0; i < 3; i++) {
112                  if (pts[i].fY < clip.fTop) {
113                      pts[i].fY = clip.fTop;
114                  }
115              }
116          }
117      }
118  
119      // are we partially below
120      if (pts[2].fY > clip.fBottom) {
121          if (chopMonoQuadAtY(pts, clip.fBottom, &t)) {
122              SkChopQuadAt(pts, tmp, t);
123              // clamp to clean up imprecise numerics in the chop
124              clamp_le(tmp[1].fY, clip.fBottom);
125              tmp[2].fY = clip.fBottom;
126  
127              pts[1] = tmp[1];
128              pts[2] = tmp[2];
129          } else {
130              // if chopMonoQuadAtY failed, then we may have hit inexact numerics
131              // so we just clamp against the bottom
132              for (int i = 0; i < 3; i++) {
133                  if (pts[i].fY > clip.fBottom) {
134                      pts[i].fY = clip.fBottom;
135                  }
136              }
137          }
138      }
139  }
140  
141  // srcPts[] must be monotonic in X and Y
clipMonoQuad(const SkPoint srcPts[3],const SkRect & clip)142  void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) {
143      SkPoint pts[3];
144      bool reverse = sort_increasing_Y(pts, srcPts, 3);
145  
146      // are we completely above or below
147      if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
148          return;
149      }
150  
151      // Now chop so that pts is contained within clip in Y
152      chop_quad_in_Y(pts, clip);
153  
154      if (pts[0].fX > pts[2].fX) {
155          SkTSwap<SkPoint>(pts[0], pts[2]);
156          reverse = !reverse;
157      }
158      SkASSERT(pts[0].fX <= pts[1].fX);
159      SkASSERT(pts[1].fX <= pts[2].fX);
160  
161      // Now chop in X has needed, and record the segments
162  
163      if (pts[2].fX <= clip.fLeft) {  // wholly to the left
164          this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
165          return;
166      }
167      if (pts[0].fX >= clip.fRight) {  // wholly to the right
168          if (!this->canCullToTheRight()) {
169              this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
170          }
171          return;
172      }
173  
174      SkScalar t;
175      SkPoint tmp[5]; // for SkChopQuadAt
176  
177      // are we partially to the left
178      if (pts[0].fX < clip.fLeft) {
179          if (chopMonoQuadAtX(pts, clip.fLeft, &t)) {
180              SkChopQuadAt(pts, tmp, t);
181              this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse);
182              // clamp to clean up imprecise numerics in the chop
183              tmp[2].fX = clip.fLeft;
184              clamp_ge(tmp[3].fX, clip.fLeft);
185  
186              pts[0] = tmp[2];
187              pts[1] = tmp[3];
188          } else {
189              // if chopMonoQuadAtY failed, then we may have hit inexact numerics
190              // so we just clamp against the left
191              this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
192              return;
193          }
194      }
195  
196      // are we partially to the right
197      if (pts[2].fX > clip.fRight) {
198          if (chopMonoQuadAtX(pts, clip.fRight, &t)) {
199              SkChopQuadAt(pts, tmp, t);
200              // clamp to clean up imprecise numerics in the chop
201              clamp_le(tmp[1].fX, clip.fRight);
202              tmp[2].fX = clip.fRight;
203  
204              this->appendQuad(tmp, reverse);
205              this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse);
206          } else {
207              // if chopMonoQuadAtY failed, then we may have hit inexact numerics
208              // so we just clamp against the right
209              this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
210          }
211      } else {    // wholly inside the clip
212          this->appendQuad(pts, reverse);
213      }
214  }
215  
clipQuad(const SkPoint srcPts[3],const SkRect & clip)216  bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) {
217      fCurrPoint = fPoints;
218      fCurrVerb = fVerbs;
219  
220      SkRect  bounds;
221      bounds.set(srcPts, 3);
222  
223      if (!quick_reject(bounds, clip)) {
224          SkPoint monoY[5];
225          int countY = SkChopQuadAtYExtrema(srcPts, monoY);
226          for (int y = 0; y <= countY; y++) {
227              SkPoint monoX[5];
228              int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX);
229              for (int x = 0; x <= countX; x++) {
230                  this->clipMonoQuad(&monoX[x * 2], clip);
231                  SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
232                  SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
233              }
234          }
235      }
236  
237      *fCurrVerb = SkPath::kDone_Verb;
238      fCurrPoint = fPoints;
239      fCurrVerb = fVerbs;
240      return SkPath::kDone_Verb != fVerbs[0];
241  }
242  
243  ///////////////////////////////////////////////////////////////////////////////
244  
mono_cubic_closestT(const SkScalar src[],SkScalar x)245  static SkScalar mono_cubic_closestT(const SkScalar src[], SkScalar x) {
246      SkScalar t = 0.5f;
247      SkScalar lastT;
248      SkScalar bestT  SK_INIT_TO_AVOID_WARNING;
249      SkScalar step = 0.25f;
250      SkScalar D = src[0];
251      SkScalar A = src[6] + 3*(src[2] - src[4]) - D;
252      SkScalar B = 3*(src[4] - src[2] - src[2] + D);
253      SkScalar C = 3*(src[2] - D);
254      x -= D;
255      SkScalar closest = SK_ScalarMax;
256      do {
257          SkScalar loc = ((A * t + B) * t + C) * t;
258          SkScalar dist = SkScalarAbs(loc - x);
259          if (closest > dist) {
260              closest = dist;
261              bestT = t;
262          }
263          lastT = t;
264          t += loc < x ? step : -step;
265          step *= 0.5f;
266      } while (closest > 0.25f && lastT != t);
267      return bestT;
268  }
269  
chop_mono_cubic_at_y(SkPoint src[4],SkScalar y,SkPoint dst[7])270  static void chop_mono_cubic_at_y(SkPoint src[4], SkScalar y, SkPoint dst[7]) {
271      if (SkChopMonoCubicAtY(src, y, dst)) {
272          return;
273      }
274      SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fY, y));
275  }
276  
277  // Modify pts[] in place so that it is clipped in Y to the clip rect
chop_cubic_in_Y(SkPoint pts[4],const SkRect & clip)278  static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) {
279  
280      // are we partially above
281      if (pts[0].fY < clip.fTop) {
282          SkPoint tmp[7];
283          chop_mono_cubic_at_y(pts, clip.fTop, tmp);
284  
285          /*
286           *  For a large range in the points, we can do a poor job of chopping, such that the t
287           *  we computed resulted in the lower cubic still being partly above the clip.
288           *
289           *  If just the first or first 2 Y values are above the fTop, we can just smash them
290           *  down. If the first 3 Ys are above fTop, we can't smash all 3, as that can really
291           *  distort the cubic. In this case, we take the first output (tmp[3..6] and treat it as
292           *  a guess, and re-chop against fTop. Then we fall through to checking if we need to
293           *  smash the first 1 or 2 Y values.
294           */
295          if (tmp[3].fY < clip.fTop && tmp[4].fY < clip.fTop && tmp[5].fY < clip.fTop) {
296              SkPoint tmp2[4];
297              memcpy(tmp2, &tmp[3].fX, 4 * sizeof(SkPoint));
298              chop_mono_cubic_at_y(tmp2, clip.fTop, tmp);
299          }
300  
301          // tmp[3, 4].fY should all be to the below clip.fTop.
302          // Since we can't trust the numerics of the chopper, we force those conditions now
303          tmp[3].fY = clip.fTop;
304          clamp_ge(tmp[4].fY, clip.fTop);
305  
306          pts[0] = tmp[3];
307          pts[1] = tmp[4];
308          pts[2] = tmp[5];
309      }
310  
311      // are we partially below
312      if (pts[3].fY > clip.fBottom) {
313          SkPoint tmp[7];
314          chop_mono_cubic_at_y(pts, clip.fBottom, tmp);
315          tmp[3].fY = clip.fBottom;
316          clamp_le(tmp[2].fY, clip.fBottom);
317  
318          pts[1] = tmp[1];
319          pts[2] = tmp[2];
320          pts[3] = tmp[3];
321      }
322  }
323  
chop_mono_cubic_at_x(SkPoint src[4],SkScalar x,SkPoint dst[7])324  static void chop_mono_cubic_at_x(SkPoint src[4], SkScalar x, SkPoint dst[7]) {
325      if (SkChopMonoCubicAtX(src, x, dst)) {
326          return;
327      }
328      SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fX, x));
329  }
330  
331  // srcPts[] must be monotonic in X and Y
clipMonoCubic(const SkPoint src[4],const SkRect & clip)332  void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) {
333      SkPoint pts[4];
334      bool reverse = sort_increasing_Y(pts, src, 4);
335  
336      // are we completely above or below
337      if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
338          return;
339      }
340  
341      // Now chop so that pts is contained within clip in Y
342      chop_cubic_in_Y(pts, clip);
343  
344      if (pts[0].fX > pts[3].fX) {
345          SkTSwap<SkPoint>(pts[0], pts[3]);
346          SkTSwap<SkPoint>(pts[1], pts[2]);
347          reverse = !reverse;
348      }
349  
350      // Now chop in X has needed, and record the segments
351  
352      if (pts[3].fX <= clip.fLeft) {  // wholly to the left
353          this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
354          return;
355      }
356      if (pts[0].fX >= clip.fRight) {  // wholly to the right
357          if (!this->canCullToTheRight()) {
358              this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
359          }
360          return;
361      }
362  
363      // are we partially to the left
364      if (pts[0].fX < clip.fLeft) {
365          SkPoint tmp[7];
366          chop_mono_cubic_at_x(pts, clip.fLeft, tmp);
367          this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse);
368  
369          // tmp[3, 4].fX should all be to the right of clip.fLeft.
370          // Since we can't trust the numerics of
371          // the chopper, we force those conditions now
372          tmp[3].fX = clip.fLeft;
373          clamp_ge(tmp[4].fX, clip.fLeft);
374  
375          pts[0] = tmp[3];
376          pts[1] = tmp[4];
377          pts[2] = tmp[5];
378      }
379  
380      // are we partially to the right
381      if (pts[3].fX > clip.fRight) {
382          SkPoint tmp[7];
383          chop_mono_cubic_at_x(pts, clip.fRight, tmp);
384          tmp[3].fX = clip.fRight;
385          clamp_le(tmp[2].fX, clip.fRight);
386  
387          this->appendCubic(tmp, reverse);
388          this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse);
389      } else {    // wholly inside the clip
390          this->appendCubic(pts, reverse);
391      }
392  }
393  
compute_cubic_bounds(const SkPoint pts[4])394  static SkRect compute_cubic_bounds(const SkPoint pts[4]) {
395      SkRect r;
396      r.set(pts, 4);
397      return r;
398  }
399  
too_big_for_reliable_float_math(const SkRect & r)400  static bool too_big_for_reliable_float_math(const SkRect& r) {
401      // limit set as the largest float value for which we can still reliably compute things like
402      // - chopping at XY extrema
403      // - chopping at Y or X values for clipping
404      //
405      // Current value chosen just by experiment. Larger (and still succeeds) is always better.
406      //
407      const SkScalar limit = 1 << 22;
408      return r.fLeft < -limit || r.fTop < -limit || r.fRight > limit || r.fBottom > limit;
409  }
410  
clipCubic(const SkPoint srcPts[4],const SkRect & clip)411  bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) {
412      fCurrPoint = fPoints;
413      fCurrVerb = fVerbs;
414  
415      const SkRect bounds = compute_cubic_bounds(srcPts);
416      // check if we're clipped out vertically
417      if (bounds.fBottom > clip.fTop && bounds.fTop < clip.fBottom) {
418          if (too_big_for_reliable_float_math(bounds)) {
419              // can't safely clip the cubic, so we give up and draw a line (which we can safely clip)
420              //
421              // If we rewrote chopcubicat*extrema and chopmonocubic using doubles, we could very
422              // likely always handle the cubic safely, but (it seems) at a big loss in speed, so
423              // we'd only want to take that alternate impl if needed. Perhaps a TODO to try it.
424              //
425              return this->clipLine(srcPts[0], srcPts[3], clip);
426          } else {
427              SkPoint monoY[10];
428              int countY = SkChopCubicAtYExtrema(srcPts, monoY);
429              for (int y = 0; y <= countY; y++) {
430                  SkPoint monoX[10];
431                  int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX);
432                  for (int x = 0; x <= countX; x++) {
433                      this->clipMonoCubic(&monoX[x * 3], clip);
434                      SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
435                      SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
436                  }
437              }
438          }
439      }
440  
441      *fCurrVerb = SkPath::kDone_Verb;
442      fCurrPoint = fPoints;
443      fCurrVerb = fVerbs;
444      return SkPath::kDone_Verb != fVerbs[0];
445  }
446  
447  ///////////////////////////////////////////////////////////////////////////////
448  
appendLine(SkPoint p0,SkPoint p1)449  void SkEdgeClipper::appendLine(SkPoint p0, SkPoint p1) {
450      *fCurrVerb++ = SkPath::kLine_Verb;
451      fCurrPoint[0] = p0;
452      fCurrPoint[1] = p1;
453      fCurrPoint += 2;
454  }
455  
appendVLine(SkScalar x,SkScalar y0,SkScalar y1,bool reverse)456  void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1,
457                                  bool reverse) {
458      *fCurrVerb++ = SkPath::kLine_Verb;
459  
460      if (reverse) {
461          SkTSwap<SkScalar>(y0, y1);
462      }
463      fCurrPoint[0].set(x, y0);
464      fCurrPoint[1].set(x, y1);
465      fCurrPoint += 2;
466  }
467  
appendQuad(const SkPoint pts[3],bool reverse)468  void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) {
469      *fCurrVerb++ = SkPath::kQuad_Verb;
470  
471      if (reverse) {
472          fCurrPoint[0] = pts[2];
473          fCurrPoint[2] = pts[0];
474      } else {
475          fCurrPoint[0] = pts[0];
476          fCurrPoint[2] = pts[2];
477      }
478      fCurrPoint[1] = pts[1];
479      fCurrPoint += 3;
480  }
481  
appendCubic(const SkPoint pts[4],bool reverse)482  void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) {
483      *fCurrVerb++ = SkPath::kCubic_Verb;
484  
485      if (reverse) {
486          for (int i = 0; i < 4; i++) {
487              fCurrPoint[i] = pts[3 - i];
488          }
489      } else {
490          memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint));
491      }
492      fCurrPoint += 4;
493  }
494  
next(SkPoint pts[])495  SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) {
496      SkPath::Verb verb = *fCurrVerb;
497  
498      switch (verb) {
499          case SkPath::kLine_Verb:
500              memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint));
501              fCurrPoint += 2;
502              fCurrVerb += 1;
503              break;
504          case SkPath::kQuad_Verb:
505              memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint));
506              fCurrPoint += 3;
507              fCurrVerb += 1;
508              break;
509          case SkPath::kCubic_Verb:
510              memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint));
511              fCurrPoint += 4;
512              fCurrVerb += 1;
513              break;
514          case SkPath::kDone_Verb:
515              break;
516          default:
517              SkDEBUGFAIL("unexpected verb in quadclippper2 iter");
518              break;
519      }
520      return verb;
521  }
522  
523  ///////////////////////////////////////////////////////////////////////////////
524  
525  #ifdef SK_DEBUG
assert_monotonic(const SkScalar coord[],int count)526  static void assert_monotonic(const SkScalar coord[], int count) {
527      if (coord[0] > coord[(count - 1) * 2]) {
528          for (int i = 1; i < count; i++) {
529              SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]);
530          }
531      } else if (coord[0] < coord[(count - 1) * 2]) {
532          for (int i = 1; i < count; i++) {
533              SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]);
534          }
535      } else {
536          for (int i = 1; i < count; i++) {
537              SkASSERT(coord[2 * (i - 1)] == coord[i * 2]);
538          }
539      }
540  }
541  
sk_assert_monotonic_y(const SkPoint pts[],int count)542  void sk_assert_monotonic_y(const SkPoint pts[], int count) {
543      if (count > 1) {
544          assert_monotonic(&pts[0].fY, count);
545      }
546  }
547  
sk_assert_monotonic_x(const SkPoint pts[],int count)548  void sk_assert_monotonic_x(const SkPoint pts[], int count) {
549      if (count > 1) {
550          assert_monotonic(&pts[0].fX, count);
551      }
552  }
553  #endif
554