|  | /* | 
|  | * Copyright 2011 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "SkLineClipper.h" | 
|  |  | 
|  | template <typename T> T pin_unsorted(T value, T limit0, T limit1) { | 
|  | if (limit1 < limit0) { | 
|  | SkTSwap(limit0, limit1); | 
|  | } | 
|  | // now the limits are sorted | 
|  | SkASSERT(limit0 <= limit1); | 
|  |  | 
|  | if (value < limit0) { | 
|  | value = limit0; | 
|  | } else if (value > limit1) { | 
|  | value = limit1; | 
|  | } | 
|  | return value; | 
|  | } | 
|  |  | 
|  | // return X coordinate of intersection with horizontal line at Y | 
|  | static SkScalar sect_with_horizontal(const SkPoint src[2], SkScalar Y) { | 
|  | SkScalar dy = src[1].fY - src[0].fY; | 
|  | if (SkScalarNearlyZero(dy)) { | 
|  | return SkScalarAve(src[0].fX, src[1].fX); | 
|  | } else { | 
|  | // need the extra precision so we don't compute a value that exceeds | 
|  | // our original limits | 
|  | double X0 = src[0].fX; | 
|  | double Y0 = src[0].fY; | 
|  | double X1 = src[1].fX; | 
|  | double Y1 = src[1].fY; | 
|  | double result = X0 + ((double)Y - Y0) * (X1 - X0) / (Y1 - Y0); | 
|  |  | 
|  | // The computed X value might still exceed [X0..X1] due to quantum flux | 
|  | // when the doubles were added and subtracted, so we have to pin the | 
|  | // answer :( | 
|  | return (float)pin_unsorted(result, X0, X1); | 
|  | } | 
|  | } | 
|  |  | 
|  | // return Y coordinate of intersection with vertical line at X | 
|  | static SkScalar sect_with_vertical(const SkPoint src[2], SkScalar X) { | 
|  | SkScalar dx = src[1].fX - src[0].fX; | 
|  | if (SkScalarNearlyZero(dx)) { | 
|  | return SkScalarAve(src[0].fY, src[1].fY); | 
|  | } else { | 
|  | // need the extra precision so we don't compute a value that exceeds | 
|  | // our original limits | 
|  | double X0 = src[0].fX; | 
|  | double Y0 = src[0].fY; | 
|  | double X1 = src[1].fX; | 
|  | double Y1 = src[1].fY; | 
|  | double result = Y0 + ((double)X - X0) * (Y1 - Y0) / (X1 - X0); | 
|  | return (float)result; | 
|  | } | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | static inline bool nestedLT(SkScalar a, SkScalar b, SkScalar dim) { | 
|  | return a <= b && (a < b || dim > 0); | 
|  | } | 
|  |  | 
|  | // returns true if outer contains inner, even if inner is empty. | 
|  | // note: outer.contains(inner) always returns false if inner is empty. | 
|  | static inline bool containsNoEmptyCheck(const SkRect& outer, | 
|  | const SkRect& inner) { | 
|  | return  outer.fLeft <= inner.fLeft && outer.fTop <= inner.fTop && | 
|  | outer.fRight >= inner.fRight && outer.fBottom >= inner.fBottom; | 
|  | } | 
|  |  | 
|  | bool SkLineClipper::IntersectLine(const SkPoint src[2], const SkRect& clip, | 
|  | SkPoint dst[2]) { | 
|  | SkRect bounds; | 
|  |  | 
|  | bounds.set(src[0], src[1]); | 
|  | if (containsNoEmptyCheck(clip, bounds)) { | 
|  | if (src != dst) { | 
|  | memcpy(dst, src, 2 * sizeof(SkPoint)); | 
|  | } | 
|  | return true; | 
|  | } | 
|  | // check for no overlap, and only permit coincident edges if the line | 
|  | // and the edge are colinear | 
|  | if (nestedLT(bounds.fRight, clip.fLeft, bounds.width()) || | 
|  | nestedLT(clip.fRight, bounds.fLeft, bounds.width()) || | 
|  | nestedLT(bounds.fBottom, clip.fTop, bounds.height()) || | 
|  | nestedLT(clip.fBottom, bounds.fTop, bounds.height())) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | int index0, index1; | 
|  |  | 
|  | if (src[0].fY < src[1].fY) { | 
|  | index0 = 0; | 
|  | index1 = 1; | 
|  | } else { | 
|  | index0 = 1; | 
|  | index1 = 0; | 
|  | } | 
|  |  | 
|  | SkPoint tmp[2]; | 
|  | memcpy(tmp, src, sizeof(tmp)); | 
|  |  | 
|  | // now compute Y intersections | 
|  | if (tmp[index0].fY < clip.fTop) { | 
|  | tmp[index0].set(sect_with_horizontal(src, clip.fTop), clip.fTop); | 
|  | } | 
|  | if (tmp[index1].fY > clip.fBottom) { | 
|  | tmp[index1].set(sect_with_horizontal(src, clip.fBottom), clip.fBottom); | 
|  | } | 
|  |  | 
|  | if (tmp[0].fX < tmp[1].fX) { | 
|  | index0 = 0; | 
|  | index1 = 1; | 
|  | } else { | 
|  | index0 = 1; | 
|  | index1 = 0; | 
|  | } | 
|  |  | 
|  | // check for quick-reject in X again, now that we may have been chopped | 
|  | if ((tmp[index1].fX <= clip.fLeft || tmp[index0].fX >= clip.fRight) && | 
|  | tmp[index0].fX < tmp[index1].fX) { | 
|  | // only reject if we have a non-zero width | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (tmp[index0].fX < clip.fLeft) { | 
|  | tmp[index0].set(clip.fLeft, sect_with_vertical(src, clip.fLeft)); | 
|  | } | 
|  | if (tmp[index1].fX > clip.fRight) { | 
|  | tmp[index1].set(clip.fRight, sect_with_vertical(src, clip.fRight)); | 
|  | } | 
|  | #ifdef SK_DEBUG | 
|  | bounds.set(tmp[0], tmp[1]); | 
|  | SkASSERT(containsNoEmptyCheck(clip, bounds)); | 
|  | #endif | 
|  | memcpy(dst, tmp, sizeof(tmp)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | #ifdef SK_DEBUG | 
|  | // return value between the two limits, where the limits are either ascending | 
|  | // or descending. | 
|  | static bool is_between_unsorted(SkScalar value, | 
|  | SkScalar limit0, SkScalar limit1) { | 
|  | if (limit0 < limit1) { | 
|  | return limit0 <= value && value <= limit1; | 
|  | } else { | 
|  | return limit1 <= value && value <= limit0; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | int SkLineClipper::ClipLine(const SkPoint pts[], const SkRect& clip, SkPoint lines[], | 
|  | bool canCullToTheRight) { | 
|  | int index0, index1; | 
|  |  | 
|  | if (pts[0].fY < pts[1].fY) { | 
|  | index0 = 0; | 
|  | index1 = 1; | 
|  | } else { | 
|  | index0 = 1; | 
|  | index1 = 0; | 
|  | } | 
|  |  | 
|  | // Check if we're completely clipped out in Y (above or below | 
|  |  | 
|  | if (pts[index1].fY <= clip.fTop) {  // we're above the clip | 
|  | return 0; | 
|  | } | 
|  | if (pts[index0].fY >= clip.fBottom) {  // we're below the clip | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // Chop in Y to produce a single segment, stored in tmp[0..1] | 
|  |  | 
|  | SkPoint tmp[2]; | 
|  | memcpy(tmp, pts, sizeof(tmp)); | 
|  |  | 
|  | // now compute intersections | 
|  | if (pts[index0].fY < clip.fTop) { | 
|  | tmp[index0].set(sect_with_horizontal(pts, clip.fTop), clip.fTop); | 
|  | SkASSERT(is_between_unsorted(tmp[index0].fX, pts[0].fX, pts[1].fX)); | 
|  | } | 
|  | if (tmp[index1].fY > clip.fBottom) { | 
|  | tmp[index1].set(sect_with_horizontal(pts, clip.fBottom), clip.fBottom); | 
|  | SkASSERT(is_between_unsorted(tmp[index1].fX, pts[0].fX, pts[1].fX)); | 
|  | } | 
|  |  | 
|  | // Chop it into 1..3 segments that are wholly within the clip in X. | 
|  |  | 
|  | // temp storage for up to 3 segments | 
|  | SkPoint resultStorage[kMaxPoints]; | 
|  | SkPoint* result;    // points to our results, either tmp or resultStorage | 
|  | int lineCount = 1; | 
|  | bool reverse; | 
|  |  | 
|  | if (pts[0].fX < pts[1].fX) { | 
|  | index0 = 0; | 
|  | index1 = 1; | 
|  | reverse = false; | 
|  | } else { | 
|  | index0 = 1; | 
|  | index1 = 0; | 
|  | reverse = true; | 
|  | } | 
|  |  | 
|  | if (tmp[index1].fX <= clip.fLeft) {  // wholly to the left | 
|  | tmp[0].fX = tmp[1].fX = clip.fLeft; | 
|  | result = tmp; | 
|  | reverse = false; | 
|  | } else if (tmp[index0].fX >= clip.fRight) {    // wholly to the right | 
|  | if (canCullToTheRight) { | 
|  | return 0; | 
|  | } | 
|  | tmp[0].fX = tmp[1].fX = clip.fRight; | 
|  | result = tmp; | 
|  | reverse = false; | 
|  | } else { | 
|  | result = resultStorage; | 
|  | SkPoint* r = result; | 
|  |  | 
|  | if (tmp[index0].fX < clip.fLeft) { | 
|  | r->set(clip.fLeft, tmp[index0].fY); | 
|  | r += 1; | 
|  | r->set(clip.fLeft, sect_with_vertical(tmp, clip.fLeft)); | 
|  | SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY)); | 
|  | } else { | 
|  | *r = tmp[index0]; | 
|  | } | 
|  | r += 1; | 
|  |  | 
|  | if (tmp[index1].fX > clip.fRight) { | 
|  | r->set(clip.fRight, sect_with_vertical(tmp, clip.fRight)); | 
|  | SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY)); | 
|  | r += 1; | 
|  | r->set(clip.fRight, tmp[index1].fY); | 
|  | } else { | 
|  | *r = tmp[index1]; | 
|  | } | 
|  |  | 
|  | lineCount = SkToInt(r - result); | 
|  | } | 
|  |  | 
|  | // Now copy the results into the caller's lines[] parameter | 
|  | if (reverse) { | 
|  | // copy the pts in reverse order to maintain winding order | 
|  | for (int i = 0; i <= lineCount; i++) { | 
|  | lines[lineCount - i] = result[i]; | 
|  | } | 
|  | } else { | 
|  | memcpy(lines, result, (lineCount + 1) * sizeof(SkPoint)); | 
|  | } | 
|  | return lineCount; | 
|  | } |