add specialty strokeRect() to SkStroke, which can return much cleaner results
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 20 Nov 2012 19:00:28 +0000 (19:00 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 20 Nov 2012 19:00:28 +0000 (19:00 +0000)
Review URL: https://codereview.appspot.com/6843093

git-svn-id: http://skia.googlecode.com/svn/trunk@6510 2bbb7eff-a529-9590-31e7-b0007b416f81

gm/strokerect.cpp [new file with mode: 0644]
gyp/gmslides.gypi
gyp/tests.gyp
src/core/SkStroke.cpp
src/core/SkStroke.h
tests/StrokeTest.cpp [new file with mode: 0644]

diff --git a/gm/strokerect.cpp b/gm/strokerect.cpp
new file mode 100644 (file)
index 0000000..f075600
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPath.h"
+
+#define STROKE_WIDTH    SkIntToScalar(20)
+
+static void draw_path(SkCanvas* canvas, const SkPath& path, const SkRect& rect, SkPaint::Join join) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+
+    paint.setColor(SK_ColorGRAY);
+    paint.setStrokeWidth(STROKE_WIDTH);
+    paint.setStrokeJoin(join);
+    canvas->drawRect(rect, paint);
+
+    paint.setStrokeWidth(0);
+    paint.setColor(SK_ColorRED);
+    canvas->drawPath(path, paint);
+
+    paint.setStrokeWidth(3);
+    paint.setStrokeJoin(SkPaint::kMiter_Join);
+    int n = path.countPoints();
+    SkAutoTArray<SkPoint> points(n);
+    path.getPoints(points.get(), n);
+    canvas->drawPoints(SkCanvas::kPoints_PointMode, n, points.get(), paint);
+}
+
+/*
+ *  Test calling SkStroker for rectangles. Cases to cover:
+ *
+ *  geometry: normal, small (smaller than stroke-width), empty, inverted
+ *  joint-type for the corners
+ */
+class StrokeRectGM : public skiagm::GM {
+public:
+    StrokeRectGM() {}
+
+protected:
+    virtual SkString onShortName() {
+        return SkString("strokerect");
+    }
+
+    virtual SkISize onISize() {
+        return SkISize::Make(1024, 480);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        canvas->drawColor(SK_ColorWHITE);
+        canvas->translate(STROKE_WIDTH*3/2, STROKE_WIDTH*3/2);
+
+        SkPaint paint;
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setStrokeWidth(STROKE_WIDTH);
+
+        static const SkPaint::Join gJoins[] = {
+            SkPaint::kMiter_Join, SkPaint::kRound_Join, SkPaint::kBevel_Join
+        };
+
+        static const SkScalar W = 80;
+        static const SkScalar H = 110;
+        static const SkRect gRects[] = {
+            { 0, 0, W, H },
+            { W, 0, 0, H },
+            { 0, H, W, 0 },
+            { 0, 0, STROKE_WIDTH, H },
+            { 0, 0, W, STROKE_WIDTH },
+            { 0, 0, STROKE_WIDTH/2, STROKE_WIDTH/2 },
+            { 0, 0, W, 0 },
+            { 0, 0, 0, H },
+            { 0, 0, 0, 0 },
+        };
+            
+        for (size_t i = 0; i < SK_ARRAY_COUNT(gJoins); ++i) {
+            SkPaint::Join join = gJoins[i];
+            paint.setStrokeJoin(join);
+
+            SkAutoCanvasRestore acr(canvas, true);
+            for (size_t j = 0; j < SK_ARRAY_COUNT(gRects); ++j) {
+                const SkRect& r = gRects[j];
+
+                SkPath path, fillPath;
+                path.addRect(r);
+                paint.getFillPath(path, &fillPath);
+                draw_path(canvas, fillPath, r, join);
+
+                canvas->translate(W + 2 * STROKE_WIDTH, 0);
+            }
+            acr.restore();
+            canvas->translate(0, H + 2 * STROKE_WIDTH);
+        }
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new StrokeRectGM;)
+
index 61b1061..bff8391 100644 (file)
@@ -69,6 +69,7 @@
     '../gm/simpleaaclip.cpp',
     '../gm/srcmode.cpp',
     '../gm/strokefill.cpp',
+    '../gm/strokerect.cpp',
     '../gm/strokerects.cpp',
     '../gm/strokes.cpp',
     '../gm/tablecolorfilter.cpp',
index d1c0f80..85f58ea 100644 (file)
@@ -88,6 +88,7 @@
         '../tests/SrcOverTest.cpp',
         '../tests/StreamTest.cpp',
         '../tests/StringTest.cpp',
+        '../tests/StrokeTest.cpp',
         '../tests/TDLinkedListTest.cpp',
         '../tests/Test.cpp',
         '../tests/Test.h',
index 0ab1016..0d85a04 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2008 The Android Open Source Project
  *
@@ -6,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-
 #include "SkStrokerPriv.h"
 #include "SkGeometry.h"
 #include "SkPath.h"
@@ -609,6 +607,15 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
         return;
     }
 
+    // If src is really a rect, call our specialty strokeRect() method
+    {
+        SkRect rect;
+        if (src.isRect(&rect)) {
+            this->strokeRect(rect, dst);
+            return;
+        }
+    }
+
 #ifdef SK_SCALAR_IS_FIXED
     void (*proc)(SkPoint pts[], int count) = identity_proc;
     if (needs_to_shrink(src)) {
@@ -702,3 +709,81 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
     }
 }
 
+static SkPath::Direction reverse_direction(SkPath::Direction dir) {
+    SkASSERT(SkPath::kUnknown_Direction != dir);
+    return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
+}
+
+static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
+    SkPoint pts[8];
+
+    if (SkPath::kCW_Direction == dir) {
+        pts[0].set(r.fLeft, outer.fTop);
+        pts[1].set(r.fRight, outer.fTop);
+        pts[2].set(outer.fRight, r.fTop);
+        pts[3].set(outer.fRight, r.fBottom);
+        pts[4].set(r.fRight, outer.fBottom);
+        pts[5].set(r.fLeft, outer.fBottom);
+        pts[6].set(outer.fLeft, r.fBottom);
+        pts[7].set(outer.fLeft, r.fTop);
+    } else {
+        pts[7].set(r.fLeft, outer.fTop);
+        pts[6].set(r.fRight, outer.fTop);
+        pts[5].set(outer.fRight, r.fTop);
+        pts[4].set(outer.fRight, r.fBottom);
+        pts[3].set(r.fRight, outer.fBottom);
+        pts[2].set(r.fLeft, outer.fBottom);
+        pts[1].set(outer.fLeft, r.fBottom);
+        pts[0].set(outer.fLeft, r.fTop);
+    }
+    path->addPoly(pts, 8, true);
+}
+
+void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst) const {
+    SkASSERT(dst != NULL);
+    dst->reset();
+
+    SkScalar radius = SkScalarHalf(fWidth);
+    if (radius <= 0) {
+        return;
+    }
+
+    SkPath::Direction dir = SkPath::kCW_Direction;
+    SkScalar rw = origRect.width();
+    SkScalar rh = origRect.height();
+    if ((rw < 0) ^ (rh < 0)) {
+        dir = SkPath::kCCW_Direction;
+    }
+    SkRect rect(origRect);
+    rect.sort();
+    // reassign these, now that we know they'll be >= 0
+    rw = rect.width();
+    rh = rect.height();
+
+    SkRect r(rect);
+    r.outset(radius, radius);
+
+    SkPaint::Join join = (SkPaint::Join)fJoin;
+    if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
+        join = SkPaint::kBevel_Join;
+    }
+
+    switch (fJoin) {
+        case SkPaint::kMiter_Join:
+            dst->addRect(r, dir);
+            break;
+        case SkPaint::kBevel_Join:
+            addBevel(dst, rect, r, dir);
+            break;
+        case SkPaint::kRound_Join:
+            dst->addRoundRect(r, radius, radius);
+            break;
+    }
+
+    if (fWidth < SkMinScalar(rw, rh)) {
+        r = rect;
+        r.inset(radius, radius);
+        dst->addRect(r, reverse_direction(dir));
+    }
+}
+
index 46e6ba1..5f0b475 100644 (file)
@@ -40,6 +40,7 @@ public:
     bool    getDoFill() const { return SkToBool(fDoFill); }
     void    setDoFill(bool doFill) { fDoFill = SkToU8(doFill); }
 
+    void    strokeRect(const SkRect&, SkPath*) const;
     void    strokePath(const SkPath& path, SkPath*) const;
 
     ////////////////////////////////////////////////////////////////
diff --git a/tests/StrokeTest.cpp b/tests/StrokeTest.cpp
new file mode 100644 (file)
index 0000000..a6c481a
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkRect.h"
+#include "SkStroke.h"
+
+static bool equal(const SkRect& a, const SkRect& b) {
+    return  SkScalarNearlyEqual(a.left(), b.left()) &&
+            SkScalarNearlyEqual(a.top(), b.top()) &&
+            SkScalarNearlyEqual(a.right(), b.right()) &&
+            SkScalarNearlyEqual(a.bottom(), b.bottom());
+}
+
+static void test_strokerect(skiatest::Reporter* reporter) {
+    const SkScalar width = SkIntToScalar(10);
+    SkPaint paint;
+
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(width);
+
+    SkRect r = { 0, 0, SkIntToScalar(200), SkIntToScalar(100) };
+
+    SkRect outer(r);
+    outer.outset(width/2, width/2);
+
+    static const SkPaint::Join joins[] = {
+        SkPaint::kMiter_Join, SkPaint::kRound_Join, SkPaint::kBevel_Join
+    };
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(joins); ++i) {
+        paint.setStrokeJoin(joins[i]);
+
+        SkPath path, fillPath;
+        path.addRect(r);
+        paint.getFillPath(path, &fillPath);
+
+        REPORTER_ASSERT(reporter, equal(outer, fillPath.getBounds()));
+        
+        bool isMiter = SkPaint::kMiter_Join == joins[i];
+        SkRect nested[2];
+        REPORTER_ASSERT(reporter, fillPath.isNestedRects(nested) == isMiter);
+        if (isMiter) {
+            SkRect inner(r);
+            inner.inset(width/2, width/2);
+            REPORTER_ASSERT(reporter, equal(nested[0], outer));
+            REPORTER_ASSERT(reporter, equal(nested[1], inner));
+        }
+    }
+}
+
+static void TestStroke(skiatest::Reporter* reporter) {
+    test_strokerect(reporter);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("Stroke", TestStrokeClass, TestStroke)
+