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