add new patheffect for stroke-and-fill
Change-Id: I2212e547d3d15c65c20f2f8e10fda5f2ef832187
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/291041
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Mike Reed <reed@google.com>
diff --git a/gm/patheffects.cpp b/gm/patheffects.cpp
index 37715f1..695d5e2 100644
--- a/gm/patheffects.cpp
+++ b/gm/patheffects.cpp
@@ -237,3 +237,58 @@
};
DEF_GM(return new ComboPathEfectsGM;)
+#include "include/effects/SkStrokeAndFillPathEffect.h"
+
+// Test that we can replicate SkPaint::kStrokeAndFill_Style
+// with a patheffect. We expect the 2nd and 3rd columns to draw the same.
+DEF_SIMPLE_GM(stroke_and_fill_patheffect, canvas, 900, 450) {
+ const float kStrokeWidth = 20;
+
+ typedef void (*Maker)(SkPath*);
+ const Maker makers[] = {
+ [](SkPath* path) {
+ path->addOval({0, 0, 100, 100}, SkPathDirection::kCW);
+ },
+ [](SkPath* path) {
+ path->addOval({0, 0, 100, 100}, SkPathDirection::kCCW);
+ },
+ [](SkPath* path) {
+ path->moveTo(0, 0).lineTo(100, 100).lineTo(0, 100).lineTo(100, 0).close();
+ },
+ };
+
+ const struct {
+ SkPaint::Style fStyle;
+ float fWidth;
+ bool fUsePE;
+ bool fExpectStrokeAndFill;
+ } rec[] = {
+ { SkPaint::kStroke_Style, 0, false, false },
+ { SkPaint::kFill_Style, 0, true, false },
+ { SkPaint::kStroke_Style, 0, true, false },
+ { SkPaint::kStrokeAndFill_Style, kStrokeWidth, false, true },
+ { SkPaint::kStroke_Style, kStrokeWidth, true, true },
+ { SkPaint::kStrokeAndFill_Style, kStrokeWidth, true, true },
+ };
+
+ SkPaint paint;
+ canvas->translate(20, 20);
+ for (auto maker : makers) {
+ SkPath path;
+ maker(&path);
+
+ canvas->save();
+ for (const auto& r : rec) {
+ paint.setStyle(r.fStyle);
+ paint.setStrokeWidth(r.fWidth);
+ paint.setPathEffect(r.fUsePE ? SkStrokeAndFillPathEffect::Make() : nullptr);
+ paint.setColor(r.fExpectStrokeAndFill ? SK_ColorGRAY : SK_ColorBLACK);
+
+ canvas->drawPath(path, paint);
+ canvas->translate(150, 0);
+ }
+ canvas->restore();
+
+ canvas->translate(0, 150);
+ }
+}
diff --git a/include/effects/SkStrokeAndFillPathEffect.h b/include/effects/SkStrokeAndFillPathEffect.h
new file mode 100644
index 0000000..fbde649
--- /dev/null
+++ b/include/effects/SkStrokeAndFillPathEffect.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkStrokeAndFillPathEffect_DEFINED
+#define SkStrokeAndFillPathEffect_DEFINED
+
+#include "include/core/SkPaint.h"
+#include "include/core/SkPathEffect.h"
+#include "include/pathops/SkPathOps.h"
+
+class SK_API SkStrokeAndFillPathEffect {
+public:
+ /* If the paint is set to stroke, this will add the stroke and fill geometries
+ * together (hoping that the winding-direction works out).
+ *
+ * If the paint is set to fill, this effect is ignored.
+ *
+ * Note that if the paint is set to stroke and the stroke-width is 0, then
+ * this will turn the geometry into just a fill.
+ */
+ static sk_sp<SkPathEffect> Make();
+};
+
+#endif
diff --git a/src/effects/SkOpPE.h b/src/effects/SkOpPE.h
index 1f51579..db87a05 100644
--- a/src/effects/SkOpPE.h
+++ b/src/effects/SkOpPE.h
@@ -65,5 +65,20 @@
typedef SkPathEffect INHERITED;
};
+class SkStrokeAndFillPE : public SkPathEffect {
+public:
+ SkStrokeAndFillPE() {}
+
+protected:
+ void flatten(SkWriteBuffer&) const override;
+ bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override;
+ // TODO: override onComputeFastBounds (I think)
+
+private:
+ SK_FLATTENABLE_HOOKS(SkStrokeAndFillPE)
+
+ typedef SkPathEffect INHERITED;
+};
+
#endif
diff --git a/src/effects/SkOpPathEffect.cpp b/src/effects/SkOpPathEffect.cpp
index e7b8a60..733bbc4 100644
--- a/src/effects/SkOpPathEffect.cpp
+++ b/src/effects/SkOpPathEffect.cpp
@@ -121,4 +121,43 @@
return buffer.isValid() ? SkStrokePathEffect::Make(width, join, cap, miter) : nullptr;
}
+//////////////////////////////////////////////////////////////////////////////////////////////////
+#include "include/effects/SkStrokeAndFillPathEffect.h"
+#include "src/core/SkPathPriv.h"
+
+sk_sp<SkPathEffect> SkStrokeAndFillPathEffect::Make() {
+ return sk_sp<SkPathEffect>(new SkStrokeAndFillPE);
+}
+
+void SkStrokeAndFillPE::flatten(SkWriteBuffer&) const {}
+
+bool SkStrokeAndFillPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
+ const SkRect*) const {
+ // This one is weird, since we exist to allow this paint-style to go away. If we see it,
+ // just let the normal machine run its course.
+ if (rec->getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
+ *dst = src;
+ return true;
+ }
+
+ if (rec->getStyle() == SkStrokeRec::kStroke_Style) {
+ if (!rec->applyToPath(dst, src)) {
+ return false;
+ }
+ // lifted from SkStroke.cpp as an attempt to handle winding directions
+ if (SkPathPriv::CheapIsFirstDirection(src, SkPathPriv::kCCW_FirstDirection)) {
+ dst->reverseAddPath(src);
+ } else {
+ dst->addPath(src);
+ }
+ } else {
+ *dst = src;
+ }
+ rec->setFillStyle();
+ return true;
+}
+
+sk_sp<SkFlattenable> SkStrokeAndFillPE::CreateProc(SkReadBuffer& buffer) {
+ return SkStrokeAndFillPathEffect::Make();
+}
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index e277b2c..559ff0a 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -96,6 +96,7 @@
SK_REGISTER_FLATTENABLE(SkPath1DPathEffect);
SK_REGISTER_FLATTENABLE(SkPath2DPathEffect);
SK_REGISTER_FLATTENABLE(SkStrokePE);
+ SK_REGISTER_FLATTENABLE(SkStrokeAndFillPE);
SK_REGISTER_FLATTENABLE(SkTrimPE);
SkPathEffect::RegisterFlattenables();