This brings hairlines into agreement with thick strokes.
authorcaryclark <caryclark@google.com>
Wed, 16 Dec 2015 16:53:41 +0000 (08:53 -0800)
committerCommit bot <commit-bot@chromium.org>
Wed, 16 Dec 2015 16:53:41 +0000 (08:53 -0800)
Add more testing and a pixel magnification to GM.

R=reed@google.com, fmalita@chromium.org
BUG=skia:4599
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1527083002

Review URL: https://codereview.chromium.org/1527083002

gm/path_stroke_with_zero_length.cpp
src/core/SkScan_Hairline.cpp

index dc52947..6f542ba 100644 (file)
 #include "SkStream.h"
 #include "gm.h"
 
+
 // Test how short paths are stroked with various caps
-DEF_SIMPLE_GM(path_stroke_with_zero_length, canvas, 240, 120) {
-    SkPath paths[5];
-    paths[0].moveTo(30.0f, 0);  // single line segment
-    paths[0].rLineTo(30.0f, 0);
+class StrokeZeroGM : public skiagm::GM {
+    SkPath fPaths[8];
+    SkPath fClipL, fClipR, fClipS;
+
+protected:
+    void onOnceBeforeDraw() override {
+        fClipL.moveTo(0, 0);
+        fClipL.lineTo(3, 0);
+        fClipL.lineTo(2.5f, 1);
+        fClipL.lineTo(3.5f, 2.5f);
+        fClipL.lineTo(2.5f, 4);
+        fClipL.lineTo(3, 5);
+        fClipL.lineTo(0, 5);
+        fClipL.close();
+
+        fClipR.moveTo(34, 0);
+        fClipR.lineTo(34, 5);
+        fClipR.lineTo(31, 5);
+        fClipR.lineTo(30.5, 4);
+        fClipR.lineTo(31.5, 2.5);
+        fClipR.lineTo(30.5, 1);
+        fClipR.lineTo(31, 0);
+        fClipR.close();
+
+        fClipS.addRect(SkRect::MakeIWH(4, 5));
+
+        fPaths[0].moveTo(30, 0);  // single line segment
+        fPaths[0].rLineTo(30, 0);
+
+        fPaths[1].moveTo(90, 0);  // single line segment with close (does not draw caps)
+        fPaths[1].rLineTo(30, 0);
+        fPaths[1].close();
+
+        fPaths[2].moveTo(150, 0);  // zero-length line
+        fPaths[2].rLineTo(0, 0);
+
+        fPaths[3].moveTo(180, 0);  // zero-length line with close (expected not to draw)
+        fPaths[3].rLineTo(0, 0);
+        fPaths[3].close();
+
+        fPaths[4].moveTo(210, 0);  // close only, no line
+        fPaths[4].close();
+
+        fPaths[5].moveTo(30, 90);  // all combos below should draw two caps
+        fPaths[5].rLineTo(0, 0);
+        fPaths[5].moveTo(60, 90);
+        fPaths[5].rLineTo(0, 0);
+
+        fPaths[6].moveTo(90, 90);
+        fPaths[6].close();
+        fPaths[6].moveTo(120, 90);
+        fPaths[6].close();
+
+        fPaths[7].moveTo(150, 90);
+        fPaths[7].rLineTo(0, 0);
+        fPaths[7].moveTo(180, 90);
+        fPaths[7].close();
+    }
+
+
+    SkString onShortName() override {
+        return SkString("path_stroke_with_zero_length");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(1120, 840);
+    }
 
-    paths[1].moveTo(90.0f, 0);  // single line segment with close
-    paths[1].rLineTo(30.0f, 0);
-    paths[1].close();
+    void onDraw(SkCanvas* canvas) override {
+        SkPaint bkgrnd;
+        bkgrnd.setColor(SK_ColorWHITE);
+        canvas->drawRect(SkRect::MakeIWH(onISize().fWidth, onISize().fHeight), bkgrnd);
 
-    paths[2].moveTo(150.0f, 0);  // zero-length line
-    paths[2].rLineTo(0, 0);
+         auto drawPaths = [&](SkPaint& paint, int indexMask) {
+            canvas->translate(0, 30.0f);
+            int index = 0;
+            for (const SkPath& path : fPaths) {
+                if (indexMask & (1 << index)) {
+                    canvas->drawPath(path, paint);
+                }
+                if (paint.getStrokeWidth() < 2) {
+                    drawFat(canvas, path, paint, index);
+                }
+                ++index;
+            }
+        };
 
-    paths[3].moveTo(180.0f, 0);  // zero-length line with close
-    paths[3].rLineTo(0, 0);
-    paths[3].close();
+        if (false) { // debugging variant that draws a single element
+            SkScalar width = 0;
+            bool antialias = true;
 
-    paths[4].moveTo(210.0f, 0);  // close only, no line
-    paths[4].close();
+            SkPaint butt;
+            butt.setAntiAlias(antialias);
+            butt.setStyle(SkPaint::kStroke_Style);
+            butt.setStrokeWidth(width);
 
-    auto drawPaths = [&](const SkPaint& paint) {
-        canvas->translate(0, 30.0f);
-        for (const SkPath& path : paths) {
-            canvas->drawPath(path, paint);
+            SkPaint round(butt);
+            round.setStrokeCap(SkPaint::kRound_Cap);
+            drawPaths(round, 1 << 7);
+            return;
         }
-    };
-    
-    SkAutoCanvasRestore autoCanvasRestore(canvas, true);
-
-    SkPaint butt;
-    butt.setStyle(SkPaint::kStroke_Style);
-    butt.setStrokeWidth(20.0f);
-    butt.setStrokeCap(SkPaint::kButt_Cap);
-    drawPaths(butt);
-
-    SkPaint round(butt);
-    round.setStrokeCap(SkPaint::kRound_Cap);
-    drawPaths(round);
-
-    SkPaint square(butt);
-    square.setStrokeCap(SkPaint::kSquare_Cap);
-    drawPaths(square);
-}
+
+        SkScalar widths[] = { 0, .999f, 1, 1.001f, 20 };
+        bool aliases[] = { false, true };
+        for (bool antialias : aliases) {
+            canvas->save();
+            for (SkScalar width : widths) { 
+                canvas->save();
+                SkPaint butt;
+                butt.setAntiAlias(antialias);
+                butt.setStyle(SkPaint::kStroke_Style);
+                butt.setStrokeWidth(width);
+                drawPaths(butt, -1);
+
+                SkPaint round(butt);
+                round.setStrokeCap(SkPaint::kRound_Cap);
+                drawPaths(round, -1);
+
+                SkPaint square(butt);
+                square.setStrokeCap(SkPaint::kSquare_Cap);
+                drawPaths(square, -1);
+                canvas->restore();
+                canvas->translate(220, 0);
+            }
+            canvas->restore();
+            canvas->translate(0, 210);
+        }
+    }
+
+private:
+    void drawFat(SkCanvas* canvas, const SkPath& path, const SkPaint& paint, int index) {
+        const SkScalar scale = 10;
+        SkRect bounds = path.getBounds();
+        SkBitmap offscreen;
+        offscreen.allocN32Pixels(SkScalarRoundToInt(bounds.width() + 4),
+                SkScalarRoundToInt(bounds.height() + 4));
+        SkScalar pathX = bounds.fLeft - 2;
+        SkScalar pathY = bounds.fTop - 2;
+        SkMatrix cMatrix = canvas->getTotalMatrix();
+        if (!canvas->readPixels(&offscreen, SkScalarRoundToInt(pathX + cMatrix.getTranslateX()),
+                SkScalarRoundToInt(pathY + cMatrix.getTranslateY()))) {
+            return;
+        }
+
+        canvas->save();
+        SkMatrix clipM;
+        clipM.reset();
+        clipM.preScale(scale, scale);
+        clipM.postTranslate(bounds.fLeft - 17, bounds.fTop - 24.5f + 420);
+        SkPath clip;
+        if (index < 2) {
+            fClipL.transform(clipM, &clip);
+        } else {
+            fClipS.transform(clipM, &clip);
+        }
+        canvas->clipPath(clip, SkRegion::kIntersect_Op, true);
+        canvas->scale(scale, scale);
+        canvas->drawBitmap(offscreen, (bounds.fLeft - 17) / scale,
+                    (bounds.fTop - 20 + 420) / scale);
+        canvas->restore();
+
+        if (bounds.width() > 20) {
+            canvas->save();
+            clipM.reset();
+            clipM.preScale(scale, scale);
+            clipM.postTranslate(bounds.fLeft - 17 - 275, bounds.fTop - 24.5f + 420);
+            SkPath clip;
+            fClipR.transform(clipM, &clip);
+            canvas->clipPath(clip, SkRegion::kIntersect_Op, true);
+            canvas->scale(10.f, 10.f);
+            canvas->drawBitmap(offscreen, (bounds.fLeft - 17 - 275
+                    + (index >= 5 ? 5 : 0)) / scale, (bounds.fTop - 20 + 420) / scale);
+            canvas->restore();
+        }
+    }
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+DEF_GM( return new StrokeZeroGM(); )
+
index 52399c9..0209925 100644 (file)
@@ -471,6 +471,10 @@ void hair_path(const SkPath& path, const SkRasterClip& rclip, SkBlitter* blitter
             case SkPath::kClose_Verb:
                 pts[0] = lastPt;
                 pts[1] = firstPt;
+                if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) {
+                    // cap moveTo/close to match svg expectations for degenerate segments
+                    extend_pts<capStyle>(prevVerb, iter.peek(), pts, 2);
+                }
                 lineproc(pts, 2, clip, blitter);
                 break;
             case SkPath::kDone_Verb: