aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar caryclark <caryclark@google.com>2015-08-21 13:27:37 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-08-21 13:27:37 -0700
commitdd3c165828fffb369d0f4b13b48381169a0249a9 (patch)
tree91dd40e798e4a6a982b43e414102d57a7a4809b1
parent1cbdcde9116e9efb514236faf8cfa42649a041d1 (diff)
experiment with zero-length round capped line segments
If the endcap is not butt, draw the endcaps even when the line has zero length. If the dash length is zero, generate a zero length line segment. Treat a move followed by a close as a move followed by a zero-length line. R=reed@google.com,schenney@google.com BUG=422974 Review URL: https://codereview.chromium.org/1309753002
-rw-r--r--gm/strokes.cpp74
-rw-r--r--include/core/SkPaint.h9
-rw-r--r--src/core/SkPathMeasure.cpp9
-rw-r--r--src/core/SkStroke.cpp23
-rw-r--r--src/utils/SkDashPath.cpp2
5 files changed, 112 insertions, 5 deletions
diff --git a/gm/strokes.cpp b/gm/strokes.cpp
index 73823907a3..e914d0a0b7 100644
--- a/gm/strokes.cpp
+++ b/gm/strokes.cpp
@@ -8,6 +8,8 @@
#include "gm.h"
#include "SkPath.h"
#include "SkRandom.h"
+#include "SkDashPathEffect.h"
+#include "SkParsePath.h"
#define W 400
#define H 400
@@ -76,6 +78,76 @@ private:
typedef skiagm::GM INHERITED;
};
+/* See
+ https://code.google.com/p/chromium/issues/detail?id=422974 and
+ http://jsfiddle.net/1xnku3sg/2/
+ */
+class ZeroLenStrokesGM : public skiagm::GM {
+ SkPath fMoveHfPath, fMoveZfPath, fDashedfPath, fRefPath[4];
+protected:
+ void onOnceBeforeDraw() override {
+
+ SkAssertResult(SkParsePath::FromSVGString("M0,0h0M10,0h0M20,0h0", &fMoveHfPath));
+ SkAssertResult(SkParsePath::FromSVGString("M0,0zM10,0zM20,0z", &fMoveZfPath));
+ SkAssertResult(SkParsePath::FromSVGString("M0,0h25", &fDashedfPath));
+
+ for (int i = 0; i < 3; ++i) {
+ fRefPath[0].addCircle(i * 10.f, 0, 5);
+ fRefPath[1].addCircle(i * 10.f, 0, 10);
+ fRefPath[2].addRect(i * 10.f - 4, -2, i * 10.f + 4, 6);
+ fRefPath[3].addRect(i * 10.f - 10, -10, i * 10.f + 10, 10);
+ }
+ }
+
+ SkString onShortName() override {
+ return SkString("zeroPath");
+ }
+
+ SkISize onISize() override {
+ return SkISize::Make(W, H*2);
+ }
+
+ void onDraw(SkCanvas* canvas) override {
+ SkPaint fillPaint, strokePaint, dashPaint;
+ fillPaint.setAntiAlias(true);
+ strokePaint = fillPaint;
+ strokePaint.setStyle(SkPaint::kStroke_Style);
+ for (int i = 0; i < 2; ++i) {
+ fillPaint.setAlpha(255);
+ strokePaint.setAlpha(255);
+ strokePaint.setStrokeWidth(i ? 8.f : 10.f);
+ strokePaint.setStrokeCap(i ? SkPaint::kSquare_Cap : SkPaint::kRound_Cap);
+ canvas->save();
+ canvas->translate(10 + i * 100.f, 10);
+ canvas->drawPath(fMoveHfPath, strokePaint);
+ canvas->translate(0, 20);
+ canvas->drawPath(fMoveZfPath, strokePaint);
+ dashPaint = strokePaint;
+ const SkScalar intervals[] = { 0, 10 };
+ dashPaint.setPathEffect(SkDashPathEffect::Create(intervals, 2, 0))->unref();
+ SkPath fillPath;
+ dashPaint.getFillPath(fDashedfPath, &fillPath);
+ canvas->translate(0, 20);
+ canvas->drawPath(fDashedfPath, dashPaint);
+ canvas->translate(0, 20);
+ canvas->drawPath(fRefPath[i * 2], fillPaint);
+ strokePaint.setStrokeWidth(20);
+ strokePaint.setAlpha(127);
+ canvas->translate(0, 50);
+ canvas->drawPath(fMoveHfPath, strokePaint);
+ canvas->translate(0, 30);
+ canvas->drawPath(fMoveZfPath, strokePaint);
+ canvas->translate(0, 30);
+ fillPaint.setAlpha(127);
+ canvas->drawPath(fRefPath[1 + i * 2], fillPaint);
+ canvas->restore();
+ }
+ }
+
+private:
+ typedef skiagm::GM INHERITED;
+};
+
class Strokes2GM : public skiagm::GM {
SkPath fPath;
protected:
@@ -278,3 +350,5 @@ static skiagm::GMRegistry R0(F0);
static skiagm::GMRegistry R1(F1);
static skiagm::GMRegistry R2(F2);
static skiagm::GMRegistry R3(F3);
+
+DEF_GM(return SkNEW(ZeroLenStrokesGM);)
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index 1b993fc5ea..0c071ad361 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -398,6 +398,15 @@ public:
/** Cap enum specifies the settings for the paint's strokecap. This is the
treatment that is applied to the beginning and end of each non-closed
contour (e.g. lines).
+
+ If the cap is round or square, the caps are drawn when the contour has
+ a zero length. Zero length contours can be created by following moveTo
+ with a lineTo at the same point, or a moveTo followed by a close.
+
+ A dash with an on interval of zero also creates a zero length contour.
+
+ The zero length contour draws the square cap without rotation, since
+ the no direction can be inferred.
*/
enum Cap {
kButt_Cap, //!< begin/end contours with no extension
diff --git a/src/core/SkPathMeasure.cpp b/src/core/SkPathMeasure.cpp
index a5dd84048c..17ae95e801 100644
--- a/src/core/SkPathMeasure.cpp
+++ b/src/core/SkPathMeasure.cpp
@@ -314,7 +314,12 @@ static void seg_to(const SkPoint pts[], int segType,
SkASSERT(startT <= stopT);
if (startT == stopT) {
- return; // should we report this, to undo a moveTo?
+ /* if the dash as a zero-length on segment, add a corresponding zero-length line.
+ The stroke code will add end caps to zero length lines as appropriate */
+ SkPoint lastPt;
+ SkAssertResult(dst->getLastPt(&lastPt));
+ dst->lineTo(lastPt);
+ return;
}
SkPoint tmp0[7], tmp1[7];
@@ -568,7 +573,7 @@ bool SkPathMeasure::getSegment(SkScalar startD, SkScalar stopD, SkPath* dst,
if (stopD > length) {
stopD = length;
}
- if (startD >= stopD) {
+ if (startD > stopD) {
return false;
}
diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
index ede3d21055..2db5bba524 100644
--- a/src/core/SkStroke.cpp
+++ b/src/core/SkStroke.cpp
@@ -121,6 +121,9 @@ public:
SkScalar radius, SkScalar miterLimit, SkPaint::Cap,
SkPaint::Join, SkScalar resScale);
+ bool hasOnlyMoveTo() const { return 0 == fSegmentCount; }
+ SkPoint moveToPt() const { return fFirstPt; }
+
void moveTo(const SkPoint&);
void lineTo(const SkPoint&);
void quadTo(const SkPoint&, const SkPoint&);
@@ -242,7 +245,14 @@ bool SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
SkScalar prevY = fPrevPt.fY;
if (!set_normal_unitnormal(fPrevPt, currPt, fRadius, normal, unitNormal)) {
- return false;
+ if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper) {
+ return false;
+ }
+ /* Square caps and round caps draw even if the segment length is zero.
+ Since the zero length segment has no direction, set the orientation
+ to upright as the default orientation */
+ normal->set(fRadius, 0);
+ unitNormal->set(1, 0);
}
if (fSegmentCount == 0) {
@@ -356,7 +366,8 @@ void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
}
void SkPathStroker::lineTo(const SkPoint& currPt) {
- if (SkPath::IsLineDegenerate(fPrevPt, currPt, false)) {
+ if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper
+ && SkPath::IsLineDegenerate(fPrevPt, currPt, false)) {
return;
}
SkVector normal, unitNormal;
@@ -1334,6 +1345,14 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
lastSegment = SkPath::kCubic_Verb;
break;
case SkPath::kClose_Verb:
+ if (stroker.hasOnlyMoveTo() && SkPaint::kButt_Cap != this->getCap()) {
+ /* If the stroke consists of a moveTo followed by a close, treat it
+ as if it were followed by a zero-length line. Lines without length
+ can have square and round end caps. */
+ stroker.lineTo(stroker.moveToPt());
+ lastSegment = SkPath::kLine_Verb;
+ break;
+ }
stroker.close(lastSegment == SkPath::kLine_Verb);
break;
case SkPath::kDone_Verb:
diff --git a/src/utils/SkDashPath.cpp b/src/utils/SkDashPath.cpp
index 4b2b33d2c4..de249f6fff 100644
--- a/src/utils/SkDashPath.cpp
+++ b/src/utils/SkDashPath.cpp
@@ -272,7 +272,7 @@ bool SkDashPath::FilterDashPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec
while (distance < length) {
SkASSERT(dlen >= 0);
addedSegment = false;
- if (is_even(index) && dlen > 0 && !skipFirstSegment) {
+ if (is_even(index) && !skipFirstSegment) {
addedSegment = true;
++segCount;