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