Ignore fill when stroke & filling convex line-only paths
authorrobertphillips <robertphillips@google.com>
Fri, 26 Aug 2016 12:30:19 +0000 (05:30 -0700)
committerCommit bot <commit-bot@chromium.org>
Fri, 26 Aug 2016 12:30:19 +0000 (05:30 -0700)
This seems to work well for miter and bevel joins with the resulting stroke and fill path remaining convex. There seems to be an issue with round joins where the outer generated shell is usually not convex. Without this CL the resulting stroke & filled paths are always concave.

Perf-wise (on Windows):

convex-lineonly-paths-stroke-and-fill bench

(in ms)   w/o   w/CL    %decrease

8888      2.88  2.01      30.2

gpu       4.4   1.38      68.6

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2275243003

Review-Url: https://codereview.chromium.org/2275243003

gm/convex_all_line_paths.cpp
src/core/SkStroke.cpp

index eb4bdcf..a54c0e4 100644 (file)
@@ -28,12 +28,17 @@ namespace skiagm {
 // paths
 class ConvexLineOnlyPathsGM : public GM {
 public:
-    ConvexLineOnlyPathsGM() {
+    ConvexLineOnlyPathsGM(bool doStrokeAndFill) : fDoStrokeAndFill(doStrokeAndFill) {
         this->setBGColor(0xFFFFFFFF);
     }
 
 protected:
-    SkString onShortName() override { return SkString("convex-lineonly-paths"); }
+    SkString onShortName() override {
+        if (fDoStrokeAndFill) {
+            return SkString("convex-lineonly-paths-stroke-and-fill");
+        }
+        return SkString("convex-lineonly-paths");
+    }
     SkISize onISize() override { return SkISize::Make(kGMWidth, kGMHeight); }
     bool runAsBench() const override { return true; }
 
@@ -258,20 +263,35 @@ protected:
             if (offset->fX+path.getBounds().width() > kGMWidth) {
                 offset->fX = 0;
                 offset->fY += kMaxPathHeight;
+                if (fDoStrokeAndFill) {
+                    offset->fX += kStrokeWidth / 2.0f;
+                    offset->fY += kStrokeWidth / 2.0f;
+                }
             }
             center = { offset->fX + SkScalarHalf(path.getBounds().width()), offset->fY};
             offset->fX += path.getBounds().width();
+            if (fDoStrokeAndFill) {
+                offset->fX += kStrokeWidth;
+            }
         }
 
         const SkColor colors[2] = { SK_ColorBLACK, SK_ColorWHITE };
         const SkPath::Direction dirs[2] = { SkPath::kCW_Direction, SkPath::kCCW_Direction };
         const float scales[] = { 1.0f, 0.75f, 0.5f, 0.25f, 0.1f, 0.01f, 0.001f };
+        const SkPaint::Join joins[3] = { SkPaint::kRound_Join, 
+                                         SkPaint::kBevel_Join,
+                                         SkPaint::kMiter_Join };
 
         SkPaint paint;
         paint.setAntiAlias(true);
 
         for (size_t i = 0; i < SK_ARRAY_COUNT(scales); ++i) {
             SkPath path = GetPath(index, (int) i, dirs[i%2]);
+            if (fDoStrokeAndFill) {
+                paint.setStyle(SkPaint::kStrokeAndFill_Style);
+                paint.setStrokeJoin(joins[i%3]);
+                paint.setStrokeWidth(SkIntToScalar(kStrokeWidth));
+            }
 
             canvas->save();
                 canvas->translate(center.fX, center.fY);
@@ -285,6 +305,10 @@ protected:
     void onDraw(SkCanvas* canvas) override {
         // the right edge of the last drawn path
         SkPoint offset = { 0, SkScalarHalf(kMaxPathHeight) };
+        if (fDoStrokeAndFill) {
+            offset.fX += kStrokeWidth / 2.0f;
+            offset.fY += kStrokeWidth / 2.0f;
+        }
 
         for (int i = 0; i < kNumPaths; ++i) {
             this->drawPath(canvas, i, &offset);
@@ -296,6 +320,11 @@ protected:
 
             SkPaint p;
             p.setAntiAlias(true);
+            if (fDoStrokeAndFill) {
+                p.setStyle(SkPaint::kStrokeAndFill_Style);
+                p.setStrokeJoin(SkPaint::kMiter_Join);
+                p.setStrokeWidth(SkIntToScalar(kStrokeWidth));
+            }
 
             SkPath p1;
             p1.moveTo(60.8522949f, 364.671021f);
@@ -307,15 +336,19 @@ protected:
     }
 
 private:
+    static const int kStrokeWidth   = 10;
     static const int kNumPaths      = 20;
     static const int kMaxPathHeight = 100;
     static const int kGMWidth       = 512;
     static const int kGMHeight      = 512;
 
+    bool fDoStrokeAndFill;
+
     typedef GM INHERITED;
 };
 
 //////////////////////////////////////////////////////////////////////////////
 
-DEF_GM(return new ConvexLineOnlyPathsGM;)
+DEF_GM(return new ConvexLineOnlyPathsGM(false);)
+DEF_GM(return new ConvexLineOnlyPathsGM(true);)
 }
index df3e0f9..8ff7910 100644 (file)
@@ -55,7 +55,7 @@ static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, S
                                   SkScalar radius,
                                   SkVector* normal, SkVector* unitNormal) {
     if (!unitNormal->setNormalize((after.fX - before.fX) * scale,
-            (after.fY - before.fY) * scale)) {
+                                  (after.fY - before.fY) * scale)) {
         return false;
     }
     unitNormal->rotateCCW();
@@ -121,7 +121,8 @@ class SkPathStroker {
 public:
     SkPathStroker(const SkPath& src,
                   SkScalar radius, SkScalar miterLimit, SkPaint::Cap,
-                  SkPaint::Join, SkScalar resScale);
+                  SkPaint::Join, SkScalar resScale,
+                  bool canIgnoreCenter);
 
     bool hasOnlyMoveTo() const { return 0 == fSegmentCount; }
     SkPoint moveToPt() const { return fFirstPt; }
@@ -157,6 +158,7 @@ private:
     SkPoint     fFirstOuterPt;
     int         fSegmentCount;
     bool        fPrevIsLine;
+    bool        fCanIgnoreCenter;
 
     SkStrokerPriv::CapProc  fCapper;
     SkStrokerPriv::JoinProc fJoiner;
@@ -294,11 +296,19 @@ void SkPathStroker::finishContour(bool close, bool currIsLine) {
                     fFirstUnitNormal, fRadius, fInvMiterLimit,
                     fPrevIsLine, currIsLine);
             fOuter.close();
-            // now add fInner as its own contour
-            fInner.getLastPt(&pt);
-            fOuter.moveTo(pt.fX, pt.fY);
-            fOuter.reversePathTo(fInner);
-            fOuter.close();
+
+            if (fCanIgnoreCenter) {
+                if (!fOuter.getBounds().contains(fInner.getBounds())) {
+                    SkASSERT(fInner.getBounds().contains(fOuter.getBounds()));
+                    fInner.swap(fOuter);
+                }
+            } else {
+                // now add fInner as its own contour
+                fInner.getLastPt(&pt);
+                fOuter.moveTo(pt.fX, pt.fY);
+                fOuter.reversePathTo(fInner);
+                fOuter.close();
+            }
         } else {    // add caps to start and end
             // cap the end
             fInner.getLastPt(&pt);
@@ -321,9 +331,11 @@ void SkPathStroker::finishContour(bool close, bool currIsLine) {
 
 SkPathStroker::SkPathStroker(const SkPath& src,
                              SkScalar radius, SkScalar miterLimit,
-                             SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale)
+                             SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale,
+                             bool canIgnoreCenter)
         : fRadius(radius)
-        , fResScale(resScale) {
+        , fResScale(resScale)
+        , fCanIgnoreCenter(canIgnoreCenter) {
 
     /*  This is only used when join is miter_join, but we initialize it here
         so that it is always defined, to fis valgrind warnings.
@@ -1366,7 +1378,13 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
         }
     }
 
-    SkPathStroker   stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), fResScale);
+    // We can always ignore centers for stroke and fill convex line-only paths
+    // TODO: remove the line-only restriction
+    bool ignoreCenter = fDoFill && (src.getSegmentMasks() == SkPath::kLine_SegmentMask) && 
+                        src.isLastContourClosed() && src.isConvex();
+
+    SkPathStroker   stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(),
+                            fResScale, ignoreCenter);
     SkPath::Iter    iter(src, false);
     SkPath::Verb    lastSegment = SkPath::kMove_Verb;
 
@@ -1420,7 +1438,7 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
 DONE:
     stroker.done(dst, lastSegment == SkPath::kLine_Verb);
 
-    if (fDoFill) {
+    if (fDoFill && !ignoreCenter) {
         if (SkPathPriv::CheapIsFirstDirection(src, SkPathPriv::kCCW_FirstDirection)) {
             dst->reverseAddPath(src);
         } else {