xref: /aosp_15_r20/external/skia/gm/pathopsblend.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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