blob: 8d6926f322768b6a4bc8a9610912f5d76393b74c [file] [log] [blame]
bsalomon@google.com632151b2012-02-13 15:18:34 +00001/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
Ben Wagner7fde8e12019-05-01 17:28:53 -04007
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "gm/gm.h"
9#include "include/core/SkCanvas.h"
Ben Wagner7fde8e12019-05-01 17:28:53 -040010#include "include/core/SkMatrix.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050011#include "include/core/SkPaint.h"
Mike Reed06d7c9d2020-08-26 12:56:51 -040012#include "include/core/SkPathBuilder.h"
Ben Wagner7fde8e12019-05-01 17:28:53 -040013#include "include/core/SkPathEffect.h"
14#include "include/core/SkRect.h"
15#include "include/core/SkRefCnt.h"
16#include "include/core/SkScalar.h"
17#include "include/core/SkSize.h"
18#include "include/core/SkString.h"
19#include "include/core/SkTypes.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050020#include "include/effects/Sk1DPathEffect.h"
21#include "include/effects/Sk2DPathEffect.h"
22#include "include/effects/SkCornerPathEffect.h"
23#include "include/effects/SkDashPathEffect.h"
24#include "include/effects/SkDiscretePathEffect.h"
Jim Van Vertha8624432023-02-13 16:48:09 -050025#include "include/gpu/GrDirectContext.h"
Ben Wagner7fde8e12019-05-01 17:28:53 -040026#include "include/pathops/SkPathOps.h"
27
28#include <initializer_list>
bsalomon@google.com632151b2012-02-13 15:18:34 +000029
30namespace skiagm {
31
32static void compose_pe(SkPaint* paint) {
33 SkPathEffect* pe = paint->getPathEffect();
reeda4393342016-03-18 11:22:57 -070034 sk_sp<SkPathEffect> corner = SkCornerPathEffect::Make(25);
35 sk_sp<SkPathEffect> compose;
bsalomon@google.com632151b2012-02-13 15:18:34 +000036 if (pe) {
Mike Reeda07741a2017-02-25 22:34:32 -050037 compose = SkPathEffect::MakeCompose(sk_ref_sp(pe), corner);
bsalomon@google.com632151b2012-02-13 15:18:34 +000038 } else {
39 compose = corner;
40 }
reeda4393342016-03-18 11:22:57 -070041 paint->setPathEffect(compose);
bsalomon@google.com632151b2012-02-13 15:18:34 +000042}
43
44static void hair_pe(SkPaint* paint) {
45 paint->setStrokeWidth(0);
46}
47
48static void hair2_pe(SkPaint* paint) {
49 paint->setStrokeWidth(0);
50 compose_pe(paint);
51}
52
53static void stroke_pe(SkPaint* paint) {
54 paint->setStrokeWidth(12);
55 compose_pe(paint);
56}
57
58static void dash_pe(SkPaint* paint) {
59 SkScalar inter[] = { 20, 10, 10, 10 };
60 paint->setStrokeWidth(12);
Herb Derbyc37b3862022-06-21 09:49:17 -040061 paint->setPathEffect(SkDashPathEffect::Make(inter, std::size(inter), 0));
bsalomon@google.com632151b2012-02-13 15:18:34 +000062 compose_pe(paint);
63}
64
mtkleindbfd7ab2016-09-01 11:24:54 -070065constexpr int gXY[] = {
bsalomon@google.com632151b2012-02-13 15:18:34 +0000664, 0, 0, -4, 8, -4, 12, 0, 8, 4, 0, 4
67};
68
Mike Reed06d7c9d2020-08-26 12:56:51 -040069static SkPath scale(const SkPath& path, SkScalar scale) {
bsalomon@google.com632151b2012-02-13 15:18:34 +000070 SkMatrix m;
71 m.setScale(scale, scale);
Mike Reed06d7c9d2020-08-26 12:56:51 -040072 return path.makeTransform(m);
bsalomon@google.com632151b2012-02-13 15:18:34 +000073}
74
75static void one_d_pe(SkPaint* paint) {
Mike Reed06d7c9d2020-08-26 12:56:51 -040076 SkPathBuilder b;
77 b.moveTo(SkIntToScalar(gXY[0]), SkIntToScalar(gXY[1]));
Herb Derbyc37b3862022-06-21 09:49:17 -040078 for (unsigned i = 2; i < std::size(gXY); i += 2) {
Mike Reed06d7c9d2020-08-26 12:56:51 -040079 b.lineTo(SkIntToScalar(gXY[i]), SkIntToScalar(gXY[i+1]));
80 }
81 b.close().offset(SkIntToScalar(-6), 0);
82 SkPath path = scale(b.detach(), 1.5f);
rmistry@google.comae933ce2012-08-23 18:19:56 +000083
reeda4393342016-03-18 11:22:57 -070084 paint->setPathEffect(SkPath1DPathEffect::Make(path, SkIntToScalar(21), 0,
85 SkPath1DPathEffect::kRotate_Style));
bsalomon@google.com632151b2012-02-13 15:18:34 +000086 compose_pe(paint);
87}
88
89typedef void (*PE_Proc)(SkPaint*);
mtkleindbfd7ab2016-09-01 11:24:54 -070090constexpr PE_Proc gPE[] = { hair_pe, hair2_pe, stroke_pe, dash_pe, one_d_pe };
bsalomon@google.com632151b2012-02-13 15:18:34 +000091
92static void fill_pe(SkPaint* paint) {
93 paint->setStyle(SkPaint::kFill_Style);
halcanary96fcdcc2015-08-27 07:41:13 -070094 paint->setPathEffect(nullptr);
bsalomon@google.com632151b2012-02-13 15:18:34 +000095}
96
97static void discrete_pe(SkPaint* paint) {
reeda4393342016-03-18 11:22:57 -070098 paint->setPathEffect(SkDiscretePathEffect::Make(10, 4));
bsalomon@google.com632151b2012-02-13 15:18:34 +000099}
100
reeda4393342016-03-18 11:22:57 -0700101static sk_sp<SkPathEffect> MakeTileEffect() {
bsalomon@google.com632151b2012-02-13 15:18:34 +0000102 SkMatrix m;
103 m.setScale(SkIntToScalar(12), SkIntToScalar(12));
104
Mike Reed06d7c9d2020-08-26 12:56:51 -0400105 return SkPath2DPathEffect::Make(m, SkPath::Circle(0,0,5));
bsalomon@google.com632151b2012-02-13 15:18:34 +0000106}
107
108static void tile_pe(SkPaint* paint) {
reeda4393342016-03-18 11:22:57 -0700109 paint->setPathEffect(MakeTileEffect());
bsalomon@google.com632151b2012-02-13 15:18:34 +0000110}
111
mtkleindbfd7ab2016-09-01 11:24:54 -0700112constexpr PE_Proc gPE2[] = { fill_pe, discrete_pe, tile_pe };
bsalomon@google.com632151b2012-02-13 15:18:34 +0000113
114class PathEffectGM : public GM {
115public:
116 PathEffectGM() {}
117
118protected:
Leandro Lovisolo24fa2112023-08-15 19:05:17 +0000119 SkString getName() const override { return SkString("patheffect"); }
bsalomon@google.com632151b2012-02-13 15:18:34 +0000120
Leandro Lovisolo8f023882023-08-15 21:13:52 +0000121 SkISize getISize() override { return SkISize::Make(800, 600); }
bsalomon@google.com632151b2012-02-13 15:18:34 +0000122
mtklein36352bf2015-03-25 18:17:31 -0700123 void onDraw(SkCanvas* canvas) override {
bsalomon@google.com632151b2012-02-13 15:18:34 +0000124 SkPaint paint;
125 paint.setAntiAlias(true);
126 paint.setStyle(SkPaint::kStroke_Style);
127
Mike Reed06d7c9d2020-08-26 12:56:51 -0400128 SkPath path = SkPath::Polygon({
129 {20, 20},
130 {70, 120},
131 {120, 30},
132 {170, 80},
133 {240, 50},
134 }, false);
bsalomon@google.com632151b2012-02-13 15:18:34 +0000135
bsalomon@google.com632151b2012-02-13 15:18:34 +0000136 canvas->save();
Herb Derbyc37b3862022-06-21 09:49:17 -0400137 for (size_t i = 0; i < std::size(gPE); i++) {
bsalomon@google.com632151b2012-02-13 15:18:34 +0000138 gPE[i](&paint);
139 canvas->drawPath(path, paint);
140 canvas->translate(0, 75);
141 }
142 canvas->restore();
143
144 path.reset();
145 SkRect r = { 0, 0, 250, 120 };
Mike Reed06d7c9d2020-08-26 12:56:51 -0400146 path = SkPathBuilder().addOval(r, SkPathDirection::kCW)
147 .addRect(r.makeInset(50, 50), SkPathDirection::kCCW)
148 .detach();
bsalomon@google.com632151b2012-02-13 15:18:34 +0000149
150 canvas->translate(320, 20);
Herb Derbyc37b3862022-06-21 09:49:17 -0400151 for (size_t i = 0; i < std::size(gPE2); i++) {
bsalomon@google.com632151b2012-02-13 15:18:34 +0000152 gPE2[i](&paint);
153 canvas->drawPath(path, paint);
154 canvas->translate(0, 160);
155 }
bsalomon@google.com22f42b72012-03-26 14:36:55 +0000156
Robert Phillips20390c32018-08-17 11:01:03 -0400157 const SkIRect rect = SkIRect::MakeXYWH(20, 20, 60, 60);
Herb Derbyc37b3862022-06-21 09:49:17 -0400158 for (size_t i = 0; i < std::size(gPE); i++) {
bsalomon@google.com22f42b72012-03-26 14:36:55 +0000159 SkPaint p;
160 p.setAntiAlias(true);
161 p.setStyle(SkPaint::kFill_Style);
162 gPE[i](&p);
163 canvas->drawIRect(rect, p);
164 canvas->translate(75, 0);
165 }
bsalomon@google.com632151b2012-02-13 15:18:34 +0000166 }
167
168private:
John Stiles7571f9e2020-09-02 22:42:33 -0400169 using INHERITED = GM;
bsalomon@google.com632151b2012-02-13 15:18:34 +0000170};
171
Hal Canarye964c182019-01-23 10:22:01 -0500172DEF_GM( return new PathEffectGM; )
bsalomon@google.com632151b2012-02-13 15:18:34 +0000173
John Stilesa6841be2020-08-06 14:11:56 -0400174} // namespace skiagm
Mike Reed0ef539a2018-07-18 13:28:42 -0400175
176//////////////////////////////////////////////////////////////////////////////
Mike Reed0ef539a2018-07-18 13:28:42 -0400177
Tyler Dennistonf8b7c1a2021-07-13 13:22:19 -0400178#include "include/core/SkStrokeRec.h"
179#include "src/core/SkPathEffectBase.h"
180
181namespace {
182/**
183 * Example path effect using CTM. This "strokes" a single line segment with some stroke width,
184 * and then inflates the result by some number of pixels.
185 */
186class StrokeLineInflated : public SkPathEffectBase {
187public:
188 StrokeLineInflated(float strokeWidth, float pxInflate)
189 : fRadius(strokeWidth / 2.f), fPxInflate(pxInflate) {}
190
191 bool onNeedsCTM() const final { return true; }
192
193 bool onFilterPath(SkPath* dst,
194 const SkPath& src,
195 SkStrokeRec* rec,
196 const SkRect* cullR,
197 const SkMatrix& ctm) const final {
198 SkASSERT(src.countPoints() == 2);
199 const SkPoint pts[2] = {src.getPoint(0), src.getPoint(1)};
200
201 SkMatrix invCtm;
202 if (!ctm.invert(&invCtm)) {
203 return false;
204 }
205
206 // For a line segment, we can just map the (scaled) normal vector to pixel-space,
207 // increase its length by the desired number of pixels, and then map back to canvas space.
208 SkPoint n = {pts[0].fY - pts[1].fY, pts[1].fX - pts[0].fX};
209 if (!n.setLength(fRadius)) {
210 return false;
211 }
212
213 SkPoint mappedN = ctm.mapVector(n.fX, n.fY);
214 if (!mappedN.setLength(mappedN.length() + fPxInflate)) {
215 return false;
216 }
217 n = invCtm.mapVector(mappedN.fX, mappedN.fY);
218
219 dst->moveTo(pts[0] + n);
220 dst->lineTo(pts[1] + n);
221 dst->lineTo(pts[1] - n);
222 dst->lineTo(pts[0] - n);
223 dst->close();
224
225 rec->setFillStyle();
226
227 return true;
228 }
229
230protected:
231 void flatten(SkWriteBuffer&) const final {}
232
233private:
234 SK_FLATTENABLE_HOOKS(StrokeLineInflated)
235
236 bool computeFastBounds(SkRect* bounds) const final { return false; }
237
238 const float fRadius;
239 const float fPxInflate;
240};
241
242sk_sp<SkFlattenable> StrokeLineInflated::CreateProc(SkReadBuffer&) { return nullptr; }
243
244} // namespace
245
246class CTMPathEffectGM : public skiagm::GM {
247protected:
Leandro Lovisolo24fa2112023-08-15 19:05:17 +0000248 SkString getName() const override { return SkString("ctmpatheffect"); }
Tyler Dennistonf8b7c1a2021-07-13 13:22:19 -0400249
Leandro Lovisolo8f023882023-08-15 21:13:52 +0000250 SkISize getISize() override { return SkISize::Make(800, 600); }
Tyler Dennistonf8b7c1a2021-07-13 13:22:19 -0400251
252 // TODO: ctm-aware path effects are currently CPU only
Brian Salomonc759bbf2023-12-05 11:11:27 -0500253 DrawResult onGpuSetup(SkCanvas* canvas, SkString*, GraphiteTestContext*) override {
Jim Van Vertha8624432023-02-13 16:48:09 -0500254 auto dctx = GrAsDirectContext(canvas->recordingContext());
Tyler Dennistonf8b7c1a2021-07-13 13:22:19 -0400255 return dctx == nullptr ? DrawResult::kOk : DrawResult::kSkip;
256 }
257
258 void onDraw(SkCanvas* canvas) override {
259 const float strokeWidth = 16;
260 const float pxInflate = 0.5f;
261 sk_sp<SkPathEffect> pathEffect(new StrokeLineInflated(strokeWidth, pxInflate));
262
263 SkPath path;
264 path.moveTo(100, 100);
265 path.lineTo(200, 200);
266
267 // Draw the inflated path, and a scaled version, in blue.
268 SkPaint paint;
269 paint.setAntiAlias(true);
270 paint.setColor(SkColorSetA(SK_ColorBLUE, 0xff));
271 paint.setPathEffect(pathEffect);
272 canvas->drawPath(path, paint);
273 canvas->save();
274 canvas->translate(150, 0);
275 canvas->scale(2.5, 0.5f);
276 canvas->drawPath(path, paint);
277 canvas->restore();
278
279 // Draw the regular stroked version on top in green.
280 // The inflated version should be visible underneath as a blue "border".
281 paint.setPathEffect(nullptr);
282 paint.setStyle(SkPaint::kStroke_Style);
283 paint.setStrokeWidth(strokeWidth);
284 paint.setColor(SkColorSetA(SK_ColorGREEN, 0xff));
285 canvas->drawPath(path, paint);
286 canvas->save();
287 canvas->translate(150, 0);
288 canvas->scale(2.5, 0.5f);
289 canvas->drawPath(path, paint);
290 canvas->restore();
291 }
292
293private:
294 using INHERITED = GM;
295};
296DEF_GM(return new CTMPathEffectGM;)