diff options
-rw-r--r-- | gm/strokes.cpp | 28 | ||||
-rw-r--r-- | samplecode/SampleAARects.cpp | 10 | ||||
-rw-r--r-- | samplecode/SampleDither.cpp | 9 | ||||
-rw-r--r-- | src/utils/SkDashPath.cpp | 145 |
4 files changed, 142 insertions, 50 deletions
diff --git a/gm/strokes.cpp b/gm/strokes.cpp index ed13d090ff..e9faad4d6c 100644 --- a/gm/strokes.cpp +++ b/gm/strokes.cpp @@ -533,3 +533,31 @@ DEF_SIMPLE_GM(zerolinedash, canvas, 256, 256) { 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 942242b02e..c50c39f5af 100644 --- a/samplecode/SampleAARects.cpp +++ b/samplecode/SampleAARects.cpp @@ -39,14 +39,16 @@ class AARectView : public SampleView { }; public: AARectView() { - fBitmap = createBitmap(N); + } +protected: + void onOnceBeforeDraw() override { + fBitmap = createBitmap(N); fWidth = N; } -protected: // 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 @@ protected: 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 fbe25ddcaa..79adc629fa 100644 --- a/samplecode/SampleDither.cpp +++ b/samplecode/SampleDither.cpp @@ -109,6 +109,10 @@ public: SkScalar fAngle; DitherView() { + } + +protected: + void onOnceBeforeDraw() override { make_bm(&fBM); make_bm(&fBMPreDither); pre_dither(fBMPreDither); @@ -119,9 +123,8 @@ public: 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 @@ protected: 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 cbfc8f2168..17c80fcdb2 100644 --- a/src/utils/SkDashPath.cpp +++ b/src/utils/SkDashPath.cpp @@ -83,68 +83,127 @@ static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) { rect->outset(radius, radius); } -// 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, - const SkRect* cullRect, SkScalar intervalLength, - SkPath* dstPath) { - if (nullptr == cullRect) { - return false; - } +static bool clip_line(SkPoint pts[2], const SkRect& bounds, SkScalar intervalLength, + SkScalar priorPhase) { + SkVector dxy = pts[1] - pts[0]; - SkPoint pts[2]; - if (!srcPath.isLine(pts)) { + // 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 - SkRect 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) { - return false; + SkScalar minXY = (&pts[0].fX)[xyOffset]; + SkScalar maxXY = (&pts[1].fX)[xyOffset]; + bool swapped = maxXY < minXY; + if (swapped) { + SkTSwap(minXY, maxXY); } - 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) { + 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 and - // right of the bounds (keeping our new line "in phase" with the dash, + // 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 (minX < bounds.fLeft) { - minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX, - intervalLength); + if (minXY < leftTop) { + minXY = leftTop - SkScalarMod(leftTop - minXY, intervalLength); + if (!swapped) { + minXY -= priorPhase; // for rectangles, adjust by prior phase + } } - if (maxX > bounds.fRight) { - maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight, - intervalLength); + if (maxXY > rightBottom) { + maxXY = rightBottom + SkScalarMod(maxXY - rightBottom, intervalLength); + if (swapped) { + maxXY += priorPhase; // for rectangles, adjust by prior phase + } } - SkASSERT(maxX >= minX); - if (dx < 0) { - SkTSwap(minX, maxX); + SkASSERT(maxXY >= minXY); + if (swapped) { + SkTSwap(minXY, maxXY); } - pts[0].fX = minX; - pts[1].fX = maxX; + (&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. - if (minX == maxX) { - pts[1].fX += maxX * FLT_EPSILON * 32; // 16 instead of 32 does not draw; length stays zero + // 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, + const SkRect* cullRect, SkScalar intervalLength, + SkPath* dstPath) { + if (nullptr == cullRect) { + return false; + } + + SkRect bounds; + SkPoint pts[4]; + bool isLine = srcPath.isLine(pts); + bool isRect = !isLine && srcPath.isRect(nullptr); + if (!isLine && !isRect) { + return false; + } + bounds = *cullRect; + outset_for_stroke(&bounds, rec); + 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; } dstPath->moveTo(pts[0]); dstPath->lineTo(pts[1]); |