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