efficiently dash very large rectangles and very long lines
Speed up dashing when lines and rects are absurdly large.
Prior to this CL, only horizontal lines were detected.
The onOnceBeforeDraw changes are there to make debugging easier.
Also folded in a change to handle dashing of zero length lines.
R=reed@google.com, egdaniel@google.com
Bug: skia:7311
Change-Id: Ia16fb124c7a78a5cc639e612fae29c879a37da1a
Reviewed-on: https://skia-review.googlesource.com/84862
Commit-Queue: Cary Clark <caryclark@skia.org>
Reviewed-by: Mike Reed <reed@google.com>
diff --git a/gm/strokes.cpp b/gm/strokes.cpp
index ed13d09..e9faad4 100644
--- a/gm/strokes.cpp
+++ b/gm/strokes.cpp
@@ -533,3 +533,31 @@
canvas->drawLine(100, 100, 100, 100, paint);
}
+
+DEF_SIMPLE_GM(longrect_dash, canvas, 250, 250) {
+ canvas->clear(SK_ColorWHITE);
+
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB(255, 0, 0, 0));
+ paint.setStrokeWidth(5);
+ paint.setStrokeCap(SkPaint::kRound_Cap);
+ paint.setStrokeJoin(SkPaint::kBevel_Join);
+ paint.setStyle(SkPaint::kStroke_Style);
+ SkScalar dash_pattern[] = {1, 5};
+ paint.setPathEffect(SkDashPathEffect::Make(dash_pattern, 2, 0));
+ // try all combinations of stretching bounds
+ for (auto left : { 20.f, -100001.f } ) {
+ for (auto top : { 20.f, -100001.f } ) {
+ for (auto right : { 40.f, 100001.f } ) {
+ for (auto bottom : { 40.f, 100001.f } ) {
+ canvas->save();
+ canvas->clipRect({10, 10, 50, 50});
+ canvas->drawRect({left, top, right, bottom}, paint);
+ canvas->restore();
+ canvas->translate(60, 0);
+ }
+ }
+ canvas->translate(-60 * 4, 60);
+ }
+ }
+}
diff --git a/samplecode/SampleAARects.cpp b/samplecode/SampleAARects.cpp
index 942242b..c50c39f 100644
--- a/samplecode/SampleAARects.cpp
+++ b/samplecode/SampleAARects.cpp
@@ -39,14 +39,16 @@
};
public:
AARectView() {
- fBitmap = createBitmap(N);
-
- fWidth = N;
}
protected:
+ void onOnceBeforeDraw() override {
+ fBitmap = createBitmap(N);
+ fWidth = N;
+ }
+
// overrides from SkEventSink
- virtual bool onQuery(SkEvent* evt) {
+ bool onQuery(SkEvent* evt) override {
if (SampleCode::TitleQ(*evt)) {
SampleCode::TitleR(evt, "AA Rects");
return true;
@@ -54,7 +56,7 @@
return this->INHERITED::onQuery(evt);
}
- virtual void onDrawContent(SkCanvas* canvas) {
+ void onDrawContent(SkCanvas* canvas) override {
canvas->translate(SkIntToScalar(10), SkIntToScalar(10));
SkPaint bluePaint;
diff --git a/samplecode/SampleDither.cpp b/samplecode/SampleDither.cpp
index fbe25dd..79adc62 100644
--- a/samplecode/SampleDither.cpp
+++ b/samplecode/SampleDither.cpp
@@ -109,6 +109,10 @@
SkScalar fAngle;
DitherView() {
+ }
+
+protected:
+ void onOnceBeforeDraw() override {
make_bm(&fBM);
make_bm(&fBMPreDither);
pre_dither(fBMPreDither);
@@ -119,9 +123,8 @@
this->setBGColor(0xFF181818);
}
-protected:
// overrides from SkEventSink
- virtual bool onQuery(SkEvent* evt) {
+ bool onQuery(SkEvent* evt) override {
if (SampleCode::TitleQ(*evt)) {
SampleCode::TitleR(evt, "Dither");
return true;
@@ -129,7 +132,7 @@
return this->INHERITED::onQuery(evt);
}
- virtual void onDrawContent(SkCanvas* canvas) {
+ void onDrawContent(SkCanvas* canvas) override {
SkPaint paint;
SkScalar x = SkIntToScalar(10);
SkScalar y = SkIntToScalar(10);
diff --git a/src/utils/SkDashPath.cpp b/src/utils/SkDashPath.cpp
index cbfc8f2..17c80fc 100644
--- a/src/utils/SkDashPath.cpp
+++ b/src/utils/SkDashPath.cpp
@@ -83,6 +83,82 @@
rect->outset(radius, radius);
}
+static bool clip_line(SkPoint pts[2], const SkRect& bounds, SkScalar intervalLength,
+ SkScalar priorPhase) {
+ SkVector dxy = pts[1] - pts[0];
+
+ // only horizontal or vertical lines
+ if (dxy.fX && dxy.fY) {
+ return false;
+ }
+ int xyOffset = SkToBool(dxy.fY); // 0 to adjust horizontal, 1 to adjust vertical
+
+ SkScalar minXY = (&pts[0].fX)[xyOffset];
+ SkScalar maxXY = (&pts[1].fX)[xyOffset];
+ bool swapped = maxXY < minXY;
+ if (swapped) {
+ SkTSwap(minXY, maxXY);
+ }
+
+ SkASSERT(minXY <= maxXY);
+ SkScalar leftTop = (&bounds.fLeft)[xyOffset];
+ SkScalar rightBottom = (&bounds.fRight)[xyOffset];
+ if (maxXY < leftTop || minXY > rightBottom) {
+ return false;
+ }
+
+ // Now we actually perform the chop, removing the excess to the left/top and
+ // right/bottom of the bounds (keeping our new line "in phase" with the dash,
+ // hence the (mod intervalLength).
+
+ if (minXY < leftTop) {
+ minXY = leftTop - SkScalarMod(leftTop - minXY, intervalLength);
+ if (!swapped) {
+ minXY -= priorPhase; // for rectangles, adjust by prior phase
+ }
+ }
+ if (maxXY > rightBottom) {
+ maxXY = rightBottom + SkScalarMod(maxXY - rightBottom, intervalLength);
+ if (swapped) {
+ maxXY += priorPhase; // for rectangles, adjust by prior phase
+ }
+ }
+
+ SkASSERT(maxXY >= minXY);
+ if (swapped) {
+ SkTSwap(minXY, maxXY);
+ }
+ (&pts[0].fX)[xyOffset] = minXY;
+ (&pts[1].fX)[xyOffset] = maxXY;
+
+ // If line is zero-length, bump out the end by a tiny amount
+ // to draw endcaps. The bump factor is sized so that
+ // SkPoint::Distance() computes a non-zero length.
+ // Offsets SK_ScalarNearlyZero or smaller create empty paths when Iter measures length.
+ // Large values are scaled by SK_ScalarNearlyZero so significant bits change.
+ if (minXY == maxXY) {
+ (&pts[1].fX)[xyOffset] += SkTMax(1.001f, maxXY) * SK_ScalarNearlyZero;
+ }
+ return true;
+}
+
+static bool contains_inclusive(const SkRect& rect, const SkPoint& pt) {
+ return rect.fLeft <= pt.fX && pt.fX <= rect.fRight &&
+ rect.fTop <= pt.fY && pt.fY <= rect.fBottom;
+}
+
+// Returns true is b is between a and c, that is: a <= b <= c, or a >= b >= c.
+// Can perform this test with one branch by observing that, relative to b,
+// the condition is true only if one side is positive and one side is negative.
+// If the numbers are very small, the optimization may return the wrong result
+// because the multiply may generate a zero where the simple compare does not.
+// For this reason the assert does not fire when all three numbers are near zero.
+static bool between(SkScalar a, SkScalar b, SkScalar c) {
+ SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
+ || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
+ return (a - b) * (c - b) <= 0;
+}
+
// Only handles lines for now. If returns true, dstPath is the new (smaller)
// path. If returns false, then dstPath parameter is ignored.
static bool cull_path(const SkPath& srcPath, const SkStrokeRec& rec,
@@ -92,60 +168,43 @@
return false;
}
- SkPoint pts[2];
- if (!srcPath.isLine(pts)) {
+ SkRect bounds;
+ SkPoint pts[4];
+ bool isLine = srcPath.isLine(pts);
+ bool isRect = !isLine && srcPath.isRect(nullptr);
+ if (!isLine && !isRect) {
return false;
}
-
- SkRect bounds = *cullRect;
+ bounds = *cullRect;
outset_for_stroke(&bounds, rec);
-
- SkScalar dx = pts[1].x() - pts[0].x();
- SkScalar dy = pts[1].y() - pts[0].y();
-
- // just do horizontal lines for now (lazy)
- if (dy) {
+ if (isRect) {
+ // break rect into four lines, and call each one separately
+ SkPath::Iter iter(srcPath, false);
+ SkAssertResult(SkPath::kMove_Verb == iter.next(pts));
+ SkScalar priorLength = 0;
+ while (SkPath::kLine_Verb == iter.next(pts)) {
+ SkVector v = pts[1] - pts[0];
+ // if line is entirely outside clip rect, skip it
+ if (v.fX ? between(cullRect->fTop, pts[0].fY, cullRect->fBottom) :
+ between(cullRect->fLeft, pts[0].fX, cullRect->fRight)) {
+ bool skipMoveTo = contains_inclusive(*cullRect, pts[0]);
+ if (clip_line(pts, bounds, intervalLength,
+ SkScalarMod(priorLength, intervalLength))) {
+ if (0 == priorLength || !skipMoveTo) {
+ dstPath->moveTo(pts[0]);
+ }
+ dstPath->lineTo(pts[1]);
+ }
+ }
+ // keep track of all prior lengths to set phase of next line
+ priorLength += SkScalarAbs(v.fX ? v.fX : v.fY);
+ }
+ return !dstPath->isEmpty();
+ }
+ SkASSERT(isLine);
+ if (!clip_line(pts, bounds, intervalLength, 0)) {
return false;
}
-
- SkScalar minX = pts[0].fX;
- SkScalar maxX = pts[1].fX;
-
- if (dx < 0) {
- SkTSwap(minX, maxX);
- }
-
- SkASSERT(minX <= maxX);
- if (maxX < bounds.fLeft || minX > bounds.fRight) {
- return false;
- }
-
- // Now we actually perform the chop, removing the excess to the left and
- // right of the bounds (keeping our new line "in phase" with the dash,
- // hence the (mod intervalLength).
-
- if (minX < bounds.fLeft) {
- minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX,
- intervalLength);
- }
- if (maxX > bounds.fRight) {
- maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight,
- intervalLength);
- }
-
- SkASSERT(maxX >= minX);
- if (dx < 0) {
- SkTSwap(minX, maxX);
- }
- pts[0].fX = minX;
- pts[1].fX = maxX;
-
- // If line is zero-length, bump out the end by a tiny amount
- // to draw endcaps. The bump factor is sized so that
- // SkPoint::Distance() computes a non-zero length.
- if (minX == maxX) {
- pts[1].fX += maxX * FLT_EPSILON * 32; // 16 instead of 32 does not draw; length stays zero
- }
dstPath->moveTo(pts[0]);
dstPath->lineTo(pts[1]);
return true;