1 /*
2 * Copyright 2024 Google LLC
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/SkBitmap.h"
10 #include "include/core/SkBlendMode.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkPaint.h"
14 #include "include/core/SkPath.h"
15 #include "include/core/SkRect.h"
16 #include "include/pathops/SkPathOps.h"
17 #include "src/core/SkStroke.h"
18 #include "tools/ToolUtils.h"
19
cross()20 static SkPath cross() {
21 SkPath path;
22 path.addRect(15, 0, 35, 50);
23 path.addRect(0, 15, 50, 35);
24 return path;
25 }
26
circle()27 static SkPath circle() { return SkPath::Circle(25, 25, 20); }
28
29 // We implement every op except ReverseDifference: That one can be handled by swapping the paths
30 // and using the Difference logic.
31 static constexpr SkPathOp kOps[] = {
32 kDifference_SkPathOp,
33 kIntersect_SkPathOp,
34 kUnion_SkPathOp,
35 kXOR_SkPathOp,
36 };
37
38 struct OpAsBlend {
39 SkBlendMode fMode;
40 bool fInverse = false;
41 };
42
op_blend_mode(SkPathOp op)43 static OpAsBlend op_blend_mode(SkPathOp op) {
44 switch (op) {
45 case kDifference_SkPathOp:
46 return {SkBlendMode::kClear};
47 case kIntersect_SkPathOp:
48 return {SkBlendMode::kClear, /*fInverse=*/true};
49 case kUnion_SkPathOp:
50 return {SkBlendMode::kPlus};
51 case kXOR_SkPathOp:
52 return {SkBlendMode::kXor};
53 default:
54 // We don't implement kReverseDifference (see note above)
55 SkASSERT(op == kReverseDifference_SkPathOp);
56 return {SkBlendMode::kSrcOver};
57 }
58 }
59
60 DEF_SIMPLE_GM(pathops_blend, canvas, 130, 60 * std::size(kOps) + 60 + 10) {
61 // Checkerboard background to demonstrate that we're only covering the pixels we want:
62 ToolUtils::draw_checkerboard(canvas);
63
64 // Two paths that overlap in interesting ways:
65 SkPath p1 = cross();
66 SkPath p2 = circle();
67 // One path op (intersect) requires one path be drawn using inverse-fill:
68 SkPath p2inv = p2;
69 p2inv.toggleInverseFillType();
70
71 SkPaint paint;
72 paint.setAntiAlias(true);
73
74 canvas->translate(10, 10);
75
76 // Draw the two paths by themselves:
77 {
78 canvas->save();
79 canvas->drawPath(p1, paint);
80 canvas->translate(60, 0);
81 canvas->drawPath(p2, paint);
82 canvas->restore();
83 canvas->translate(0, 60);
84 }
85
86 for (SkPathOp op : kOps) {
87 canvas->save();
88
89 // Use PathOps to compute new path, then draw it:
90 {
91 SkPath opPath;
92 Op(p1, p2, op, &opPath);
93 canvas->drawPath(opPath, paint);
94 }
95
96 canvas->translate(60, 0);
97
98 // Do raster version of op
99 {
100 auto blend = op_blend_mode(op);
101 // Create a layer. We will use blending to build a mask of the shape we want here.
102 // Note that we're always going to get a SrcOver blend of the final shape when this
103 // layer is restored. The math doesn't work out for most blend modes, because we're
104 // turning the coverage of the resulting shape into the layer's alpha.
105 canvas->saveLayer(SkRect::MakeWH(50, 50), nullptr);
106
107 // We reuse this paint to apply various blend modes:
108 SkPaint p;
109 p.setAntiAlias(true);
110
111 // Draw the first shape, using SrcOver. This fills the layer with a mask of that path:
112 p.setBlendMode(SkBlendMode::kSrcOver);
113 canvas->drawPath(p1, p);
114
115 // Based on the PathOp we're emulating, we set a specific blend mode, and then fill
116 // either the second path -- or its inverse.
117 p.setBlendMode(blend.fMode);
118 canvas->drawPath(blend.fInverse ? p2inv : p2, p);
119
120 // The layer's alpha channel now contains a mask of the desired shape. Cover the entire
121 // rectangle with whatever paint we ACTUALLY want to draw (eg, blue), using kSrcIn.
122 // This will only draw where the mask was present:
123 p.setBlendMode(SkBlendMode::kSrcIn);
124 p.setColor(SK_ColorBLUE);
125 canvas->drawPaint(p);
126 canvas->restore();
127 }
128
129 canvas->restore();
130 canvas->translate(0, 60);
131 }
132 }
133