Home | History | Annotate | Download | only in core
      1 
      2 /*
      3  * Copyright 2011 Google Inc.
      4  *
      5  * Use of this source code is governed by a BSD-style license that can be
      6  * found in the LICENSE file.
      7  */
      8 #include "SkLineClipper.h"
      9 
     10 template <typename T> T pin_unsorted(T value, T limit0, T limit1) {
     11     if (limit1 < limit0) {
     12         SkTSwap(limit0, limit1);
     13     }
     14     // now the limits are sorted
     15     SkASSERT(limit0 <= limit1);
     16 
     17     if (value < limit0) {
     18         value = limit0;
     19     } else if (value > limit1) {
     20         value = limit1;
     21     }
     22     return value;
     23 }
     24 
     25 // return X coordinate of intersection with horizontal line at Y
     26 static SkScalar sect_with_horizontal(const SkPoint src[2], SkScalar Y) {
     27     SkScalar dy = src[1].fY - src[0].fY;
     28     if (SkScalarNearlyZero(dy)) {
     29         return SkScalarAve(src[0].fX, src[1].fX);
     30     } else {
     31         // need the extra precision so we don't compute a value that exceeds
     32         // our original limits
     33         double X0 = src[0].fX;
     34         double Y0 = src[0].fY;
     35         double X1 = src[1].fX;
     36         double Y1 = src[1].fY;
     37         double result = X0 + ((double)Y - Y0) * (X1 - X0) / (Y1 - Y0);
     38 
     39         // The computed X value might still exceed [X0..X1] due to quantum flux
     40         // when the doubles were added and subtracted, so we have to pin the
     41         // answer :(
     42         return (float)pin_unsorted(result, X0, X1);
     43     }
     44 }
     45 
     46 // return Y coordinate of intersection with vertical line at X
     47 static SkScalar sect_with_vertical(const SkPoint src[2], SkScalar X) {
     48     SkScalar dx = src[1].fX - src[0].fX;
     49     if (SkScalarNearlyZero(dx)) {
     50         return SkScalarAve(src[0].fY, src[1].fY);
     51     } else {
     52         // need the extra precision so we don't compute a value that exceeds
     53         // our original limits
     54         double X0 = src[0].fX;
     55         double Y0 = src[0].fY;
     56         double X1 = src[1].fX;
     57         double Y1 = src[1].fY;
     58         double result = Y0 + ((double)X - X0) * (Y1 - Y0) / (X1 - X0);
     59         return (float)result;
     60     }
     61 }
     62 
     63 ///////////////////////////////////////////////////////////////////////////////
     64 
     65 static inline bool nestedLT(SkScalar a, SkScalar b, SkScalar dim) {
     66     return a <= b && (a < b || dim > 0);
     67 }
     68 
     69 // returns true if outer contains inner, even if inner is empty.
     70 // note: outer.contains(inner) always returns false if inner is empty.
     71 static inline bool containsNoEmptyCheck(const SkRect& outer,
     72                                         const SkRect& inner) {
     73     return  outer.fLeft <= inner.fLeft && outer.fTop <= inner.fTop &&
     74             outer.fRight >= inner.fRight && outer.fBottom >= inner.fBottom;
     75 }
     76 
     77 bool SkLineClipper::IntersectLine(const SkPoint src[2], const SkRect& clip,
     78                                   SkPoint dst[2]) {
     79     SkRect bounds;
     80 
     81     bounds.set(src, 2);
     82     if (containsNoEmptyCheck(clip, bounds)) {
     83         if (src != dst) {
     84             memcpy(dst, src, 2 * sizeof(SkPoint));
     85         }
     86         return true;
     87     }
     88     // check for no overlap, and only permit coincident edges if the line
     89     // and the edge are colinear
     90     if (nestedLT(bounds.fRight, clip.fLeft, bounds.width()) ||
     91         nestedLT(clip.fRight, bounds.fLeft, bounds.width()) ||
     92         nestedLT(bounds.fBottom, clip.fTop, bounds.height()) ||
     93         nestedLT(clip.fBottom, bounds.fTop, bounds.height())) {
     94         return false;
     95     }
     96 
     97     int index0, index1;
     98 
     99     if (src[0].fY < src[1].fY) {
    100         index0 = 0;
    101         index1 = 1;
    102     } else {
    103         index0 = 1;
    104         index1 = 0;
    105     }
    106 
    107     SkPoint tmp[2];
    108     memcpy(tmp, src, sizeof(tmp));
    109 
    110     // now compute Y intersections
    111     if (tmp[index0].fY < clip.fTop) {
    112         tmp[index0].set(sect_with_horizontal(src, clip.fTop), clip.fTop);
    113     }
    114     if (tmp[index1].fY > clip.fBottom) {
    115         tmp[index1].set(sect_with_horizontal(src, clip.fBottom), clip.fBottom);
    116     }
    117 
    118     if (tmp[0].fX < tmp[1].fX) {
    119         index0 = 0;
    120         index1 = 1;
    121     } else {
    122         index0 = 1;
    123         index1 = 0;
    124     }
    125 
    126     // check for quick-reject in X again, now that we may have been chopped
    127     if ((tmp[index1].fX <= clip.fLeft || tmp[index0].fX >= clip.fRight) &&
    128         tmp[index0].fX < tmp[index1].fX) {
    129         // only reject if we have a non-zero width
    130         return false;
    131     }
    132 
    133     if (tmp[index0].fX < clip.fLeft) {
    134         tmp[index0].set(clip.fLeft, sect_with_vertical(src, clip.fLeft));
    135     }
    136     if (tmp[index1].fX > clip.fRight) {
    137         tmp[index1].set(clip.fRight, sect_with_vertical(src, clip.fRight));
    138     }
    139 #ifdef SK_DEBUG
    140     bounds.set(tmp, 2);
    141     SkASSERT(containsNoEmptyCheck(clip, bounds));
    142 #endif
    143     memcpy(dst, tmp, sizeof(tmp));
    144     return true;
    145 }
    146 
    147 #ifdef SK_DEBUG
    148 // return value between the two limits, where the limits are either ascending
    149 // or descending.
    150 static bool is_between_unsorted(SkScalar value,
    151                                 SkScalar limit0, SkScalar limit1) {
    152     if (limit0 < limit1) {
    153         return limit0 <= value && value <= limit1;
    154     } else {
    155         return limit1 <= value && value <= limit0;
    156     }
    157 }
    158 #endif
    159 
    160 #ifdef SK_DEBUG
    161 // This is an example of why we need to pin the result computed in
    162 // sect_with_horizontal. If we didn't explicitly pin, is_between_unsorted would
    163 // fail.
    164 //
    165 static void sect_with_horizontal_test_for_pin_results() {
    166     const SkPoint pts[] = {
    167         { -540000,    -720000 },
    168         { -9.10000017e-05f, 9.99999996e-13f }
    169     };
    170     float x = sect_with_horizontal(pts, 0);
    171     SkASSERT(is_between_unsorted(x, pts[0].fX, pts[1].fX));
    172 }
    173 #endif
    174 
    175 int SkLineClipper::ClipLine(const SkPoint pts[], const SkRect& clip,
    176                             SkPoint lines[]) {
    177 #ifdef SK_DEBUG
    178     {
    179         static bool gOnce;
    180         if (!gOnce) {
    181             sect_with_horizontal_test_for_pin_results();
    182             gOnce = true;
    183         }
    184     }
    185 #endif
    186 
    187     int index0, index1;
    188 
    189     if (pts[0].fY < pts[1].fY) {
    190         index0 = 0;
    191         index1 = 1;
    192     } else {
    193         index0 = 1;
    194         index1 = 0;
    195     }
    196 
    197     // Check if we're completely clipped out in Y (above or below
    198 
    199     if (pts[index1].fY <= clip.fTop) {  // we're above the clip
    200         return 0;
    201     }
    202     if (pts[index0].fY >= clip.fBottom) {  // we're below the clip
    203         return 0;
    204     }
    205 
    206     // Chop in Y to produce a single segment, stored in tmp[0..1]
    207 
    208     SkPoint tmp[2];
    209     memcpy(tmp, pts, sizeof(tmp));
    210 
    211     // now compute intersections
    212     if (pts[index0].fY < clip.fTop) {
    213         tmp[index0].set(sect_with_horizontal(pts, clip.fTop), clip.fTop);
    214         SkASSERT(is_between_unsorted(tmp[index0].fX, pts[0].fX, pts[1].fX));
    215     }
    216     if (tmp[index1].fY > clip.fBottom) {
    217         tmp[index1].set(sect_with_horizontal(pts, clip.fBottom), clip.fBottom);
    218         SkASSERT(is_between_unsorted(tmp[index1].fX, pts[0].fX, pts[1].fX));
    219     }
    220 
    221     // Chop it into 1..3 segments that are wholly within the clip in X.
    222 
    223     // temp storage for up to 3 segments
    224     SkPoint resultStorage[kMaxPoints];
    225     SkPoint* result;    // points to our results, either tmp or resultStorage
    226     int lineCount = 1;
    227     bool reverse;
    228 
    229     if (pts[0].fX < pts[1].fX) {
    230         index0 = 0;
    231         index1 = 1;
    232         reverse = false;
    233     } else {
    234         index0 = 1;
    235         index1 = 0;
    236         reverse = true;
    237     }
    238 
    239     if (tmp[index1].fX <= clip.fLeft) {  // wholly to the left
    240         tmp[0].fX = tmp[1].fX = clip.fLeft;
    241         result = tmp;
    242         reverse = false;
    243     } else if (tmp[index0].fX >= clip.fRight) {    // wholly to the right
    244         tmp[0].fX = tmp[1].fX = clip.fRight;
    245         result = tmp;
    246         reverse = false;
    247     } else {
    248         result = resultStorage;
    249         SkPoint* r = result;
    250 
    251         if (tmp[index0].fX < clip.fLeft) {
    252             r->set(clip.fLeft, tmp[index0].fY);
    253             r += 1;
    254             r->set(clip.fLeft, sect_with_vertical(tmp, clip.fLeft));
    255             SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY));
    256         } else {
    257             *r = tmp[index0];
    258         }
    259         r += 1;
    260 
    261         if (tmp[index1].fX > clip.fRight) {
    262             r->set(clip.fRight, sect_with_vertical(tmp, clip.fRight));
    263             SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY));
    264             r += 1;
    265             r->set(clip.fRight, tmp[index1].fY);
    266         } else {
    267             *r = tmp[index1];
    268         }
    269 
    270         lineCount = SkToInt(r - result);
    271     }
    272 
    273     // Now copy the results into the caller's lines[] parameter
    274     if (reverse) {
    275         // copy the pts in reverse order to maintain winding order
    276         for (int i = 0; i <= lineCount; i++) {
    277             lines[lineCount - i] = result[i];
    278         }
    279     } else {
    280         memcpy(lines, result, (lineCount + 1) * sizeof(SkPoint));
    281     }
    282     return lineCount;
    283 }
    284