xref: /aosp_15_r20/external/skia/gm/cubicpaths.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2011 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  */
7 
8 #include "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkMatrix.h"
13 #include "include/core/SkPaint.h"
14 #include "include/core/SkPath.h"
15 #include "include/core/SkPoint.h"
16 #include "include/core/SkRect.h"
17 #include "include/core/SkScalar.h"
18 #include "include/core/SkSize.h"
19 #include "include/core/SkString.h"
20 #include "include/core/SkTypeface.h"
21 #include "include/core/SkTypes.h"
22 #include "include/effects/SkGradientShader.h"
23 #include "src/base/SkRandom.h"
24 #include "tools/ToolUtils.h"
25 #include "tools/fonts/FontToolUtils.h"
26 
27 // https://bug.skia.org/1316 shows that this cubic, when slightly clipped, creates big
28 // (incorrect) changes to its control points.
29 class ClippedCubicGM : public skiagm::GM {
getName() const30     SkString getName() const override { return SkString("clippedcubic"); }
31 
getISize()32     SkISize getISize() override { return {1240, 390}; }
33 
onDraw(SkCanvas * canvas)34     void onDraw(SkCanvas* canvas) override {
35         SkPath path;
36         path.moveTo(0, 0);
37         path.cubicTo(140, 150, 40, 10, 170, 150);
38 
39         SkPaint paint;
40         SkRect bounds = path.getBounds();
41 
42         for (SkScalar dy = -1; dy <= 1; dy += 1) {
43             canvas->save();
44             for (SkScalar dx = -1; dx <= 1; dx += 1) {
45                 canvas->save();
46                 canvas->clipRect(bounds);
47                 canvas->translate(dx, dy);
48                 canvas->drawPath(path, paint);
49                 canvas->restore();
50 
51                 canvas->translate(bounds.width(), 0);
52             }
53             canvas->restore();
54             canvas->translate(0, bounds.height());
55         }
56     }
57 };
58 
59 
60 class ClippedCubic2GM : public skiagm::GM {
getName() const61     SkString getName() const override { return SkString("clippedcubic2"); }
62 
getISize()63     SkISize getISize() override { return {1240, 390}; }
64 
onDraw(SkCanvas * canvas)65     void onDraw(SkCanvas* canvas) override {
66         canvas->save();
67         canvas->translate(-2, 120);
68         drawOne(canvas, fPath, SkRect::MakeLTRB(0, 0, 80, 150));
69         canvas->translate(0, 170);
70         drawOne(canvas, fPath, SkRect::MakeLTRB(0, 0, 80, 100));
71         canvas->translate(0, 170);
72         drawOne(canvas, fPath, SkRect::MakeLTRB(0, 0, 30, 150));
73         canvas->translate(0, 170);
74         drawOne(canvas, fPath, SkRect::MakeLTRB(0, 0, 10, 150));
75         canvas->restore();
76         canvas->save();
77         canvas->translate(20, -2);
78         drawOne(canvas, fFlipped, SkRect::MakeLTRB(0, 0, 150, 80));
79         canvas->translate(170, 0);
80         drawOne(canvas, fFlipped, SkRect::MakeLTRB(0, 0, 100, 80));
81         canvas->translate(170, 0);
82         drawOne(canvas, fFlipped, SkRect::MakeLTRB(0, 0, 150, 30));
83         canvas->translate(170, 0);
84         drawOne(canvas, fFlipped, SkRect::MakeLTRB(0, 0, 150, 10));
85         canvas->restore();
86     }
87 
drawOne(SkCanvas * canvas,const SkPath & path,const SkRect & clip)88     void drawOne(SkCanvas* canvas, const SkPath& path, const SkRect& clip) {
89         SkPaint framePaint, fillPaint;
90         framePaint.setStyle(SkPaint::kStroke_Style);
91         canvas->drawRect(clip, framePaint);
92         canvas->drawPath(path, framePaint);
93         canvas->save();
94         canvas->clipRect(clip);
95         canvas->drawPath(path, fillPaint);
96         canvas->restore();
97     }
98 
onOnceBeforeDraw()99     void onOnceBeforeDraw() override {
100         fPath.moveTo(69.7030518991886f, 0);
101         fPath.cubicTo( 69.7030518991886f, 21.831149999999997f,
102                 58.08369508178456f, 43.66448333333333f, 34.8449814469765f, 65.5f);
103         fPath.cubicTo( 11.608591683531916f, 87.33115f, -0.010765133872116195f, 109.16448333333332f,
104                 -0.013089005235602302f, 131);
105         fPath.close();
106         fFlipped = fPath;
107         SkMatrix matrix;
108         matrix.reset();
109         matrix.setScaleX(0);
110         matrix.setScaleY(0);
111         matrix.setSkewX(1);
112         matrix.setSkewY(1);
113         fFlipped.transform(matrix);
114     }
115 
116     SkPath fPath;
117     SkPath fFlipped;
118 private:
119     using INHERITED = skiagm::GM;
120 };
121 
122 class CubicPathGM : public skiagm::GM {
getName() const123     SkString getName() const override { return SkString("cubicpath"); }
124 
getISize()125     SkISize getISize() override { return {1240, 390}; }
126 
drawPath(SkPath & path,SkCanvas * canvas,SkColor color,const SkRect & clip,SkPaint::Cap cap,SkPaint::Join join,SkPaint::Style style,SkPathFillType fill,SkScalar strokeWidth)127     void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
128                   const SkRect& clip,SkPaint::Cap cap, SkPaint::Join join,
129                   SkPaint::Style style, SkPathFillType fill,
130                   SkScalar strokeWidth) {
131         path.setFillType(fill);
132         SkPaint paint;
133         paint.setStrokeCap(cap);
134         paint.setStrokeWidth(strokeWidth);
135         paint.setStrokeJoin(join);
136         paint.setColor(color);
137         paint.setStyle(style);
138         canvas->save();
139         canvas->clipRect(clip);
140         canvas->drawPath(path, paint);
141         canvas->restore();
142     }
143 
onDraw(SkCanvas * canvas)144     void onDraw(SkCanvas* canvas) override {
145         struct FillAndName {
146             SkPathFillType fFill;
147             const char*      fName;
148         };
149         constexpr FillAndName gFills[] = {
150             {SkPathFillType::kWinding, "Winding"},
151             {SkPathFillType::kEvenOdd, "Even / Odd"},
152             {SkPathFillType::kInverseWinding, "Inverse Winding"},
153             {SkPathFillType::kInverseEvenOdd, "Inverse Even / Odd"},
154         };
155         struct StyleAndName {
156             SkPaint::Style fStyle;
157             const char*    fName;
158         };
159         constexpr StyleAndName gStyles[] = {
160             {SkPaint::kFill_Style, "Fill"},
161             {SkPaint::kStroke_Style, "Stroke"},
162             {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
163         };
164         struct CapAndName {
165             SkPaint::Cap  fCap;
166             SkPaint::Join fJoin;
167             const char*   fName;
168         };
169         constexpr CapAndName gCaps[] = {
170             {SkPaint::kButt_Cap, SkPaint::kBevel_Join, "Butt"},
171             {SkPaint::kRound_Cap, SkPaint::kRound_Join, "Round"},
172             {SkPaint::kSquare_Cap, SkPaint::kBevel_Join, "Square"}
173         };
174         struct PathAndName {
175             SkPath      fPath;
176             const char* fName;
177         };
178         PathAndName path;
179         path.fPath.moveTo(25*SK_Scalar1, 10*SK_Scalar1);
180         path.fPath.cubicTo(40*SK_Scalar1, 20*SK_Scalar1,
181                            60*SK_Scalar1, 20*SK_Scalar1,
182                            75*SK_Scalar1, 10*SK_Scalar1);
183         path.fName = "moveTo-cubic";
184 
185         SkPaint titlePaint;
186         titlePaint.setColor(SK_ColorBLACK);
187         titlePaint.setAntiAlias(true);
188         SkFont     font(ToolUtils::DefaultPortableTypeface(), 15);
189         const char title[] = "Cubic Drawn Into Rectangle Clips With "
190                              "Indicated Style, Fill and Linecaps, with stroke width 10";
191         canvas->drawString(title, 20, 20, font, titlePaint);
192 
193         SkRandom rand;
194         SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
195         canvas->save();
196         canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
197         canvas->save();
198         for (size_t cap = 0; cap < std::size(gCaps); ++cap) {
199             if (0 < cap) {
200                 canvas->translate((rect.width() + 40 * SK_Scalar1) * std::size(gStyles), 0);
201             }
202             canvas->save();
203             for (size_t fill = 0; fill < std::size(gFills); ++fill) {
204                 if (0 < fill) {
205                     canvas->translate(0, rect.height() + 40 * SK_Scalar1);
206                 }
207                 canvas->save();
208                 for (size_t style = 0; style < std::size(gStyles); ++style) {
209                     if (0 < style) {
210                         canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
211                     }
212 
213                     SkColor color = 0xff007000;
214                     this->drawPath(path.fPath, canvas, color, rect,
215                                     gCaps[cap].fCap, gCaps[cap].fJoin, gStyles[style].fStyle,
216                                     gFills[fill].fFill, SK_Scalar1*10);
217 
218                     SkPaint rectPaint;
219                     rectPaint.setColor(SK_ColorBLACK);
220                     rectPaint.setStyle(SkPaint::kStroke_Style);
221                     rectPaint.setStrokeWidth(-1);
222                     rectPaint.setAntiAlias(true);
223                     canvas->drawRect(rect, rectPaint);
224 
225                     SkPaint labelPaint;
226                     labelPaint.setColor(color);
227                     font.setSize(10);
228                     canvas->drawString(gStyles[style].fName, 0, rect.height() + 12, font, labelPaint);
229                     canvas->drawString(gFills[fill].fName, 0, rect.height() + 24, font, labelPaint);
230                     canvas->drawString(gCaps[cap].fName, 0, rect.height() + 36, font, labelPaint);
231                 }
232                 canvas->restore();
233             }
234             canvas->restore();
235         }
236         canvas->restore();
237         canvas->restore();
238     }
239 };
240 
241 class CubicClosePathGM : public skiagm::GM {
getName() const242     SkString getName() const override { return SkString("cubicclosepath"); }
243 
getISize()244     SkISize getISize() override { return {1240, 390}; }
245 
drawPath(SkPath & path,SkCanvas * canvas,SkColor color,const SkRect & clip,SkPaint::Cap cap,SkPaint::Join join,SkPaint::Style style,SkPathFillType fill,SkScalar strokeWidth)246     void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
247                   const SkRect& clip,SkPaint::Cap cap, SkPaint::Join join,
248                   SkPaint::Style style, SkPathFillType fill,
249                   SkScalar strokeWidth) {
250         path.setFillType(fill);
251         SkPaint paint;
252         paint.setStrokeCap(cap);
253         paint.setStrokeWidth(strokeWidth);
254         paint.setStrokeJoin(join);
255         paint.setColor(color);
256         paint.setStyle(style);
257         canvas->save();
258         canvas->clipRect(clip);
259         canvas->drawPath(path, paint);
260         canvas->restore();
261     }
262 
onDraw(SkCanvas * canvas)263     void onDraw(SkCanvas* canvas) override {
264         struct FillAndName {
265             SkPathFillType fFill;
266             const char*      fName;
267         };
268         constexpr FillAndName gFills[] = {
269             {SkPathFillType::kWinding, "Winding"},
270             {SkPathFillType::kEvenOdd, "Even / Odd"},
271             {SkPathFillType::kInverseWinding, "Inverse Winding"},
272             {SkPathFillType::kInverseEvenOdd, "Inverse Even / Odd"},
273         };
274         struct StyleAndName {
275             SkPaint::Style fStyle;
276             const char*    fName;
277         };
278         constexpr StyleAndName gStyles[] = {
279             {SkPaint::kFill_Style, "Fill"},
280             {SkPaint::kStroke_Style, "Stroke"},
281             {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
282         };
283         struct CapAndName {
284             SkPaint::Cap  fCap;
285             SkPaint::Join fJoin;
286             const char*   fName;
287         };
288         constexpr CapAndName gCaps[] = {
289             {SkPaint::kButt_Cap, SkPaint::kBevel_Join, "Butt"},
290             {SkPaint::kRound_Cap, SkPaint::kRound_Join, "Round"},
291             {SkPaint::kSquare_Cap, SkPaint::kBevel_Join, "Square"}
292         };
293         struct PathAndName {
294             SkPath      fPath;
295             const char* fName;
296         };
297         PathAndName path;
298         path.fPath.moveTo(25*SK_Scalar1, 10*SK_Scalar1);
299         path.fPath.cubicTo(40*SK_Scalar1, 20*SK_Scalar1,
300                            60*SK_Scalar1, 20*SK_Scalar1,
301                            75*SK_Scalar1, 10*SK_Scalar1);
302         path.fPath.close();
303         path.fName = "moveTo-cubic-close";
304 
305         SkPaint titlePaint;
306         titlePaint.setColor(SK_ColorBLACK);
307         titlePaint.setAntiAlias(true);
308         SkFont     font(ToolUtils::DefaultPortableTypeface(), 15);
309         const char title[] = "Cubic Closed Drawn Into Rectangle Clips With "
310                              "Indicated Style, Fill and Linecaps, with stroke width 10";
311         canvas->drawString(title, 20, 20, font, titlePaint);
312 
313         SkRandom rand;
314         SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
315         canvas->save();
316         canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
317         canvas->save();
318         for (size_t cap = 0; cap < std::size(gCaps); ++cap) {
319             if (0 < cap) {
320                 canvas->translate((rect.width() + 40 * SK_Scalar1) * std::size(gStyles), 0);
321             }
322             canvas->save();
323             for (size_t fill = 0; fill < std::size(gFills); ++fill) {
324                 if (0 < fill) {
325                     canvas->translate(0, rect.height() + 40 * SK_Scalar1);
326                 }
327                 canvas->save();
328                 for (size_t style = 0; style < std::size(gStyles); ++style) {
329                     if (0 < style) {
330                         canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
331                     }
332 
333                     SkColor color = 0xff007000;
334                     this->drawPath(path.fPath, canvas, color, rect,
335                                     gCaps[cap].fCap, gCaps[cap].fJoin, gStyles[style].fStyle,
336                                     gFills[fill].fFill, SK_Scalar1*10);
337 
338                     SkPaint rectPaint;
339                     rectPaint.setColor(SK_ColorBLACK);
340                     rectPaint.setStyle(SkPaint::kStroke_Style);
341                     rectPaint.setStrokeWidth(-1);
342                     rectPaint.setAntiAlias(true);
343                     canvas->drawRect(rect, rectPaint);
344 
345                     SkPaint labelPaint;
346                     labelPaint.setColor(color);
347                     labelPaint.setAntiAlias(true);
348                     font.setSize(10);
349                     canvas->drawString(gStyles[style].fName, 0, rect.height() + 12, font, labelPaint);
350                     canvas->drawString(gFills[fill].fName, 0, rect.height() + 24, font, labelPaint);
351                     canvas->drawString(gCaps[cap].fName, 0, rect.height() + 36, font, labelPaint);
352                 }
353                 canvas->restore();
354             }
355             canvas->restore();
356         }
357         canvas->restore();
358         canvas->restore();
359     }
360 };
361 
362 class CubicPathShaderGM : public skiagm::GM {
getName() const363     SkString getName() const override { return SkString("cubicpath_shader"); }
364 
getISize()365     SkISize getISize() override { return {1240, 390}; }
366 
drawPath(SkPath & path,SkCanvas * canvas,const SkRect & clip,SkPaint::Cap cap,SkPaint::Join join,SkPaint::Style style,SkPathFillType fill,SkScalar strokeWidth)367     void drawPath(SkPath& path,SkCanvas* canvas,
368                   const SkRect& clip,SkPaint::Cap cap, SkPaint::Join join,
369                   SkPaint::Style style, SkPathFillType fill,
370                   SkScalar strokeWidth) {
371         const SkScalar s = 50.f;
372         const SkPoint     kPts[] = { { 0, 0 }, { s, s } };
373         const SkScalar    kPos[] = { 0, SK_Scalar1/2, SK_Scalar1 };
374         const SkColor  kColors[] = {0x80F00080, 0xF0F08000, 0x800080F0 };
375 
376         path.setFillType(fill);
377 
378         SkPaint paint;
379         paint.setStrokeCap(cap);
380         paint.setStrokeWidth(strokeWidth);
381         paint.setStrokeJoin(join);
382         paint.setShader(SkGradientShader::MakeLinear(kPts, kColors, kPos,
383                         std::size(kColors), SkTileMode::kClamp));
384         paint.setStyle(style);
385         canvas->save();
386         canvas->clipRect(clip);
387         canvas->drawPath(path, paint);
388         canvas->restore();
389     }
390 
onDraw(SkCanvas * canvas)391     void onDraw(SkCanvas* canvas) override {
392         struct FillAndName {
393             SkPathFillType fFill;
394             const char*      fName;
395         };
396         constexpr FillAndName gFills[] = {
397             {SkPathFillType::kWinding, "Winding"},
398             {SkPathFillType::kEvenOdd, "Even / Odd"},
399             {SkPathFillType::kInverseWinding, "Inverse Winding"},
400             {SkPathFillType::kInverseEvenOdd, "Inverse Even / Odd"},
401         };
402         struct StyleAndName {
403             SkPaint::Style fStyle;
404             const char*    fName;
405         };
406         constexpr StyleAndName gStyles[] = {
407             {SkPaint::kFill_Style, "Fill"},
408             {SkPaint::kStroke_Style, "Stroke"},
409             {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
410         };
411         struct CapAndName {
412             SkPaint::Cap  fCap;
413             SkPaint::Join fJoin;
414             const char*   fName;
415         };
416         constexpr CapAndName gCaps[] = {
417             {SkPaint::kButt_Cap, SkPaint::kBevel_Join, "Butt"},
418             {SkPaint::kRound_Cap, SkPaint::kRound_Join, "Round"},
419             {SkPaint::kSquare_Cap, SkPaint::kBevel_Join, "Square"}
420         };
421         struct PathAndName {
422             SkPath      fPath;
423             const char* fName;
424         };
425         PathAndName path;
426         path.fPath.moveTo(25*SK_Scalar1, 10*SK_Scalar1);
427         path.fPath.cubicTo(40*SK_Scalar1, 20*SK_Scalar1,
428                            60*SK_Scalar1, 20*SK_Scalar1,
429                            75*SK_Scalar1, 10*SK_Scalar1);
430         path.fName = "moveTo-cubic";
431 
432         SkPaint titlePaint;
433         titlePaint.setColor(SK_ColorBLACK);
434         titlePaint.setAntiAlias(true);
435         SkFont     font(ToolUtils::DefaultPortableTypeface(), 15);
436         const char title[] = "Cubic Drawn Into Rectangle Clips With "
437                              "Indicated Style, Fill and Linecaps, with stroke width 10";
438         canvas->drawString(title, 20, 20, font, titlePaint);
439 
440         SkRandom rand;
441         SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
442         canvas->save();
443         canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
444         canvas->save();
445         for (size_t cap = 0; cap < std::size(gCaps); ++cap) {
446             if (0 < cap) {
447                 canvas->translate((rect.width() + 40 * SK_Scalar1) * std::size(gStyles), 0);
448             }
449             canvas->save();
450             for (size_t fill = 0; fill < std::size(gFills); ++fill) {
451                 if (0 < fill) {
452                     canvas->translate(0, rect.height() + 40 * SK_Scalar1);
453                 }
454                 canvas->save();
455                 for (size_t style = 0; style < std::size(gStyles); ++style) {
456                     if (0 < style) {
457                         canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
458                     }
459 
460                     SkColor color = 0xff007000;
461                     this->drawPath(path.fPath, canvas, rect,
462                                     gCaps[cap].fCap, gCaps[cap].fJoin, gStyles[style].fStyle,
463                                     gFills[fill].fFill, SK_Scalar1*10);
464 
465                     SkPaint rectPaint;
466                     rectPaint.setColor(SK_ColorBLACK);
467                     rectPaint.setStyle(SkPaint::kStroke_Style);
468                     rectPaint.setStrokeWidth(-1);
469                     rectPaint.setAntiAlias(true);
470                     canvas->drawRect(rect, rectPaint);
471 
472                     SkPaint labelPaint;
473                     labelPaint.setColor(color);
474                     font.setSize(10);
475                     canvas->drawString(gStyles[style].fName, 0, rect.height() + 12, font, labelPaint);
476                     canvas->drawString(gFills[fill].fName, 0, rect.height() + 24, font, labelPaint);
477                     canvas->drawString(gCaps[cap].fName, 0, rect.height() + 36, font, labelPaint);
478                 }
479                 canvas->restore();
480             }
481             canvas->restore();
482         }
483         canvas->restore();
484         canvas->restore();
485     }
486 };
487 
488 DEF_SIMPLE_GM(bug5099, canvas, 50, 50) {
489     SkPaint p;
490     p.setColor(SK_ColorRED);
491     p.setAntiAlias(true);
492     p.setStyle(SkPaint::kStroke_Style);
493     p.setStrokeWidth(10);
494 
495     SkPath path;
496     path.moveTo(6, 27);
497     path.cubicTo(31.5f, 1.5f, 3.5f, 4.5f, 29, 29);
498     canvas->drawPath(path, p);
499 }
500 
501 DEF_SIMPLE_GM(bug6083, canvas, 100, 50) {
502     SkPaint p;
503     p.setColor(SK_ColorRED);
504     p.setAntiAlias(true);
505     p.setStyle(SkPaint::kStroke_Style);
506     p.setStrokeWidth(15);
507     canvas->translate(-500, -130);
508     SkPath path;
509     path.moveTo(500.988f, 155.200f);
510     path.lineTo(526.109f, 155.200f);
511     SkPoint p1 = { 526.109f, 155.200f };
512     SkPoint p2 = { 525.968f, 212.968f };
513     SkPoint p3 = { 526.109f, 241.840f };
514     path.cubicTo(p1, p2, p3);
515     canvas->drawPath(path, p);
516     canvas->translate(50, 0);
517     path.reset();
518     p2.set(525.968f, 213.172f);
519     path.moveTo(500.988f, 155.200f);
520     path.lineTo(526.109f, 155.200f);
521     path.cubicTo(p1, p2, p3);
522     canvas->drawPath(path, p);
523 }
524 
525 //////////////////////////////////////////////////////////////////////////////
526 
527 DEF_GM( return new CubicPathGM; )
528 DEF_GM( return new CubicPathShaderGM; )
529 DEF_GM( return new CubicClosePathGM; )
530 DEF_GM( return new ClippedCubicGM; )
531 DEF_GM( return new ClippedCubic2GM; )
532