|  | /* | 
|  | * 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/gm.h" | 
|  | #include "include/core/SkCanvas.h" | 
|  | #include "include/core/SkMatrix.h" | 
|  | #include "include/core/SkPaint.h" | 
|  | #include "include/core/SkPathBuilder.h" | 
|  | #include "include/core/SkPathEffect.h" | 
|  | #include "include/core/SkRect.h" | 
|  | #include "include/core/SkRefCnt.h" | 
|  | #include "include/core/SkScalar.h" | 
|  | #include "include/core/SkSize.h" | 
|  | #include "include/core/SkString.h" | 
|  | #include "include/core/SkTypes.h" | 
|  | #include "include/effects/Sk1DPathEffect.h" | 
|  | #include "include/effects/Sk2DPathEffect.h" | 
|  | #include "include/effects/SkCornerPathEffect.h" | 
|  | #include "include/effects/SkDashPathEffect.h" | 
|  | #include "include/effects/SkDiscretePathEffect.h" | 
|  | #include "include/effects/SkOpPathEffect.h" | 
|  | #include "include/gpu/GrDirectContext.h" | 
|  | #include "include/pathops/SkPathOps.h" | 
|  |  | 
|  | #include <initializer_list> | 
|  |  | 
|  | namespace skiagm { | 
|  |  | 
|  | static void compose_pe(SkPaint* paint) { | 
|  | SkPathEffect* pe = paint->getPathEffect(); | 
|  | sk_sp<SkPathEffect> corner = SkCornerPathEffect::Make(25); | 
|  | sk_sp<SkPathEffect> compose; | 
|  | if (pe) { | 
|  | compose = SkPathEffect::MakeCompose(sk_ref_sp(pe), corner); | 
|  | } else { | 
|  | compose = corner; | 
|  | } | 
|  | paint->setPathEffect(compose); | 
|  | } | 
|  |  | 
|  | static void hair_pe(SkPaint* paint) { | 
|  | paint->setStrokeWidth(0); | 
|  | } | 
|  |  | 
|  | static void hair2_pe(SkPaint* paint) { | 
|  | paint->setStrokeWidth(0); | 
|  | compose_pe(paint); | 
|  | } | 
|  |  | 
|  | static void stroke_pe(SkPaint* paint) { | 
|  | paint->setStrokeWidth(12); | 
|  | compose_pe(paint); | 
|  | } | 
|  |  | 
|  | static void dash_pe(SkPaint* paint) { | 
|  | SkScalar inter[] = { 20, 10, 10, 10 }; | 
|  | paint->setStrokeWidth(12); | 
|  | paint->setPathEffect(SkDashPathEffect::Make(inter, std::size(inter), 0)); | 
|  | compose_pe(paint); | 
|  | } | 
|  |  | 
|  | constexpr int gXY[] = { | 
|  | 4, 0, 0, -4, 8, -4, 12, 0, 8, 4, 0, 4 | 
|  | }; | 
|  |  | 
|  | static SkPath scale(const SkPath& path, SkScalar scale) { | 
|  | SkMatrix m; | 
|  | m.setScale(scale, scale); | 
|  | return path.makeTransform(m); | 
|  | } | 
|  |  | 
|  | static void one_d_pe(SkPaint* paint) { | 
|  | SkPathBuilder b; | 
|  | b.moveTo(SkIntToScalar(gXY[0]), SkIntToScalar(gXY[1])); | 
|  | for (unsigned i = 2; i < std::size(gXY); i += 2) { | 
|  | b.lineTo(SkIntToScalar(gXY[i]), SkIntToScalar(gXY[i+1])); | 
|  | } | 
|  | b.close().offset(SkIntToScalar(-6), 0); | 
|  | SkPath path = scale(b.detach(), 1.5f); | 
|  |  | 
|  | paint->setPathEffect(SkPath1DPathEffect::Make(path, SkIntToScalar(21), 0, | 
|  | SkPath1DPathEffect::kRotate_Style)); | 
|  | compose_pe(paint); | 
|  | } | 
|  |  | 
|  | typedef void (*PE_Proc)(SkPaint*); | 
|  | constexpr PE_Proc gPE[] = { hair_pe, hair2_pe, stroke_pe, dash_pe, one_d_pe }; | 
|  |  | 
|  | static void fill_pe(SkPaint* paint) { | 
|  | paint->setStyle(SkPaint::kFill_Style); | 
|  | paint->setPathEffect(nullptr); | 
|  | } | 
|  |  | 
|  | static void discrete_pe(SkPaint* paint) { | 
|  | paint->setPathEffect(SkDiscretePathEffect::Make(10, 4)); | 
|  | } | 
|  |  | 
|  | static sk_sp<SkPathEffect> MakeTileEffect() { | 
|  | SkMatrix m; | 
|  | m.setScale(SkIntToScalar(12), SkIntToScalar(12)); | 
|  |  | 
|  | return SkPath2DPathEffect::Make(m, SkPath::Circle(0,0,5)); | 
|  | } | 
|  |  | 
|  | static void tile_pe(SkPaint* paint) { | 
|  | paint->setPathEffect(MakeTileEffect()); | 
|  | } | 
|  |  | 
|  | constexpr PE_Proc gPE2[] = { fill_pe, discrete_pe, tile_pe }; | 
|  |  | 
|  | class PathEffectGM : public GM { | 
|  | public: | 
|  | PathEffectGM() {} | 
|  |  | 
|  | protected: | 
|  |  | 
|  | SkString onShortName() override { | 
|  | return SkString("patheffect"); | 
|  | } | 
|  |  | 
|  | SkISize onISize() override { return SkISize::Make(800, 600); } | 
|  |  | 
|  | void onDraw(SkCanvas* canvas) override { | 
|  | SkPaint paint; | 
|  | paint.setAntiAlias(true); | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  |  | 
|  | SkPath path = SkPath::Polygon({ | 
|  | {20, 20}, | 
|  | {70, 120}, | 
|  | {120, 30}, | 
|  | {170, 80}, | 
|  | {240, 50}, | 
|  | }, false); | 
|  |  | 
|  | canvas->save(); | 
|  | for (size_t i = 0; i < std::size(gPE); i++) { | 
|  | gPE[i](&paint); | 
|  | canvas->drawPath(path, paint); | 
|  | canvas->translate(0, 75); | 
|  | } | 
|  | canvas->restore(); | 
|  |  | 
|  | path.reset(); | 
|  | SkRect r = { 0, 0, 250, 120 }; | 
|  | path = SkPathBuilder().addOval(r, SkPathDirection::kCW) | 
|  | .addRect(r.makeInset(50, 50), SkPathDirection::kCCW) | 
|  | .detach(); | 
|  |  | 
|  | canvas->translate(320, 20); | 
|  | for (size_t i = 0; i < std::size(gPE2); i++) { | 
|  | gPE2[i](&paint); | 
|  | canvas->drawPath(path, paint); | 
|  | canvas->translate(0, 160); | 
|  | } | 
|  |  | 
|  | const SkIRect rect = SkIRect::MakeXYWH(20, 20, 60, 60); | 
|  | for (size_t i = 0; i < std::size(gPE); i++) { | 
|  | SkPaint p; | 
|  | p.setAntiAlias(true); | 
|  | p.setStyle(SkPaint::kFill_Style); | 
|  | gPE[i](&p); | 
|  | canvas->drawIRect(rect, p); | 
|  | canvas->translate(75, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | using INHERITED = GM; | 
|  | }; | 
|  |  | 
|  | DEF_GM( return new PathEffectGM; ) | 
|  |  | 
|  | }  // namespace skiagm | 
|  |  | 
|  | ////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | class ComboPathEfectsGM : public skiagm::GM { | 
|  | public: | 
|  | ComboPathEfectsGM() {} | 
|  |  | 
|  | protected: | 
|  |  | 
|  | SkString onShortName() override { | 
|  | return SkString("combo-patheffects"); | 
|  | } | 
|  |  | 
|  | SkISize onISize() override { return SkISize::Make(360, 630); } | 
|  |  | 
|  | void onDraw(SkCanvas* canvas) override { | 
|  | SkPath path0 = SkPath::Circle(100, 100, 60), | 
|  | path1 = SkPathBuilder().moveTo(20, 20) | 
|  | .cubicTo(20, 180, 140, 0, 140, 140) | 
|  | .detach(); | 
|  |  | 
|  | sk_sp<SkPathEffect> effects[] = { | 
|  | nullptr, | 
|  | SkStrokePathEffect::Make(20, SkPaint::kRound_Join, SkPaint::kRound_Cap, 0), | 
|  | SkMergePathEffect::Make(nullptr, | 
|  | SkStrokePathEffect::Make(20, SkPaint::kRound_Join, | 
|  | SkPaint::kRound_Cap, 0), | 
|  | kDifference_SkPathOp), | 
|  | SkMergePathEffect::Make(SkMatrixPathEffect::MakeTranslate(50, 30), | 
|  | SkStrokePathEffect::Make(20, SkPaint::kRound_Join, | 
|  | SkPaint::kRound_Cap, 0), | 
|  | kReverseDifference_SkPathOp), | 
|  | }; | 
|  |  | 
|  | SkPaint wireframe; | 
|  | wireframe.setStyle(SkPaint::kStroke_Style); | 
|  | wireframe.setAntiAlias(true); | 
|  |  | 
|  | SkPaint paint; | 
|  | paint.setColor(0xFF8888FF); | 
|  | paint.setAntiAlias(true); | 
|  |  | 
|  | for (const SkPath& path : { path0, path1 }) { | 
|  | canvas->save(); | 
|  | for (const sk_sp<SkPathEffect>& pe : effects) { | 
|  | paint.setPathEffect(pe); | 
|  | canvas->drawPath(path, paint); | 
|  | canvas->drawPath(path, wireframe); | 
|  |  | 
|  | canvas->translate(0, 150); | 
|  | } | 
|  | canvas->restore(); | 
|  | canvas->translate(180, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | using INHERITED = GM; | 
|  | }; | 
|  | 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 SkPath (*Maker)(); | 
|  | const Maker makers[] = { | 
|  | []() { return SkPath::Oval({0, 0, 100, 100}, SkPathDirection::kCW); }, | 
|  | []() { return SkPath::Oval({0, 0, 100, 100}, SkPathDirection::kCCW); }, | 
|  | []() { | 
|  | const SkPoint pts[] = { | 
|  | {0, 0}, {100, 100}, {0, 100}, {100, 0}, | 
|  | }; | 
|  | return SkPath::Polygon(pts, std::size(pts), true); | 
|  | }, | 
|  | }; | 
|  |  | 
|  | 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) { | 
|  | const SkPath path = maker(); | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | ////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | #include "include/core/SkStrokeRec.h" | 
|  | #include "src/core/SkPathEffectBase.h" | 
|  |  | 
|  | namespace { | 
|  | /** | 
|  | * Example path effect using CTM. This "strokes" a single line segment with some stroke width, | 
|  | * and then inflates the result by some number of pixels. | 
|  | */ | 
|  | class StrokeLineInflated : public SkPathEffectBase { | 
|  | public: | 
|  | StrokeLineInflated(float strokeWidth, float pxInflate) | 
|  | : fRadius(strokeWidth / 2.f), fPxInflate(pxInflate) {} | 
|  |  | 
|  | bool onNeedsCTM() const final { return true; } | 
|  |  | 
|  | bool onFilterPath(SkPath* dst, | 
|  | const SkPath& src, | 
|  | SkStrokeRec* rec, | 
|  | const SkRect* cullR, | 
|  | const SkMatrix& ctm) const final { | 
|  | SkASSERT(src.countPoints() == 2); | 
|  | const SkPoint pts[2] = {src.getPoint(0), src.getPoint(1)}; | 
|  |  | 
|  | SkMatrix invCtm; | 
|  | if (!ctm.invert(&invCtm)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // For a line segment, we can just map the (scaled) normal vector to pixel-space, | 
|  | // increase its length by the desired number of pixels, and then map back to canvas space. | 
|  | SkPoint n = {pts[0].fY - pts[1].fY, pts[1].fX - pts[0].fX}; | 
|  | if (!n.setLength(fRadius)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | SkPoint mappedN = ctm.mapVector(n.fX, n.fY); | 
|  | if (!mappedN.setLength(mappedN.length() + fPxInflate)) { | 
|  | return false; | 
|  | } | 
|  | n = invCtm.mapVector(mappedN.fX, mappedN.fY); | 
|  |  | 
|  | dst->moveTo(pts[0] + n); | 
|  | dst->lineTo(pts[1] + n); | 
|  | dst->lineTo(pts[1] - n); | 
|  | dst->lineTo(pts[0] - n); | 
|  | dst->close(); | 
|  |  | 
|  | rec->setFillStyle(); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void flatten(SkWriteBuffer&) const final {} | 
|  |  | 
|  | private: | 
|  | SK_FLATTENABLE_HOOKS(StrokeLineInflated) | 
|  |  | 
|  | bool computeFastBounds(SkRect* bounds) const final { return false; } | 
|  |  | 
|  | const float fRadius; | 
|  | const float fPxInflate; | 
|  | }; | 
|  |  | 
|  | sk_sp<SkFlattenable> StrokeLineInflated::CreateProc(SkReadBuffer&) { return nullptr; } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class CTMPathEffectGM : public skiagm::GM { | 
|  | protected: | 
|  | SkString onShortName() override { return SkString("ctmpatheffect"); } | 
|  |  | 
|  | SkISize onISize() override { return SkISize::Make(800, 600); } | 
|  |  | 
|  | // TODO: ctm-aware path effects are currently CPU only | 
|  | DrawResult onGpuSetup(SkCanvas* canvas, SkString*) override { | 
|  | auto dctx = GrAsDirectContext(canvas->recordingContext()); | 
|  | return dctx == nullptr ? DrawResult::kOk : DrawResult::kSkip; | 
|  | } | 
|  |  | 
|  | void onDraw(SkCanvas* canvas) override { | 
|  | const float strokeWidth = 16; | 
|  | const float pxInflate = 0.5f; | 
|  | sk_sp<SkPathEffect> pathEffect(new StrokeLineInflated(strokeWidth, pxInflate)); | 
|  |  | 
|  | SkPath path; | 
|  | path.moveTo(100, 100); | 
|  | path.lineTo(200, 200); | 
|  |  | 
|  | // Draw the inflated path, and a scaled version, in blue. | 
|  | SkPaint paint; | 
|  | paint.setAntiAlias(true); | 
|  | paint.setColor(SkColorSetA(SK_ColorBLUE, 0xff)); | 
|  | paint.setPathEffect(pathEffect); | 
|  | canvas->drawPath(path, paint); | 
|  | canvas->save(); | 
|  | canvas->translate(150, 0); | 
|  | canvas->scale(2.5, 0.5f); | 
|  | canvas->drawPath(path, paint); | 
|  | canvas->restore(); | 
|  |  | 
|  | // Draw the regular stroked version on top in green. | 
|  | // The inflated version should be visible underneath as a blue "border". | 
|  | paint.setPathEffect(nullptr); | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | paint.setStrokeWidth(strokeWidth); | 
|  | paint.setColor(SkColorSetA(SK_ColorGREEN, 0xff)); | 
|  | canvas->drawPath(path, paint); | 
|  | canvas->save(); | 
|  | canvas->translate(150, 0); | 
|  | canvas->scale(2.5, 0.5f); | 
|  | canvas->drawPath(path, paint); | 
|  | canvas->restore(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | using INHERITED = GM; | 
|  | }; | 
|  | DEF_GM(return new CTMPathEffectGM;) |