| /* | |
| * Copyright 2024 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "gm/gm.h" | |
| #include "include/core/SkBitmap.h" | |
| #include "include/core/SkBlendMode.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/pathops/SkPathOps.h" | |
| #include "src/core/SkStroke.h" | |
| #include "tools/ToolUtils.h" | |
| static SkPath cross() { | |
| SkPathBuilder builder; | |
| builder.addRect({15, 0, 35, 50}); | |
| builder.addRect({0, 15, 50, 35}); | |
| return builder.detach(); | |
| } | |
| static SkPath circle() { return SkPath::Circle(25, 25, 20); } | |
| // We implement every op except ReverseDifference: That one can be handled by swapping the paths | |
| // and using the Difference logic. | |
| static constexpr SkPathOp kOps[] = { | |
| kDifference_SkPathOp, | |
| kIntersect_SkPathOp, | |
| kUnion_SkPathOp, | |
| kXOR_SkPathOp, | |
| }; | |
| struct OpAsBlend { | |
| SkBlendMode fMode; | |
| bool fInverse = false; | |
| }; | |
| static OpAsBlend op_blend_mode(SkPathOp op) { | |
| switch (op) { | |
| case kDifference_SkPathOp: | |
| return {SkBlendMode::kClear}; | |
| case kIntersect_SkPathOp: | |
| return {SkBlendMode::kClear, /*fInverse=*/true}; | |
| case kUnion_SkPathOp: | |
| return {SkBlendMode::kPlus}; | |
| case kXOR_SkPathOp: | |
| return {SkBlendMode::kXor}; | |
| default: | |
| // We don't implement kReverseDifference (see note above) | |
| SkASSERT(op == kReverseDifference_SkPathOp); | |
| return {SkBlendMode::kSrcOver}; | |
| } | |
| } | |
| DEF_SIMPLE_GM(pathops_blend, canvas, 130, 60 * std::size(kOps) + 60 + 10) { | |
| // Checkerboard background to demonstrate that we're only covering the pixels we want: | |
| ToolUtils::draw_checkerboard(canvas); | |
| // Two paths that overlap in interesting ways: | |
| SkPath p1 = cross(); | |
| SkPath p2 = circle(); | |
| // One path op (intersect) requires one path be drawn using inverse-fill: | |
| SkPath p2inv = p2; | |
| p2inv.toggleInverseFillType(); | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| canvas->translate(10, 10); | |
| // Draw the two paths by themselves: | |
| { | |
| canvas->save(); | |
| canvas->drawPath(p1, paint); | |
| canvas->translate(60, 0); | |
| canvas->drawPath(p2, paint); | |
| canvas->restore(); | |
| canvas->translate(0, 60); | |
| } | |
| for (SkPathOp op : kOps) { | |
| canvas->save(); | |
| // Use PathOps to compute new path, then draw it: | |
| if (auto opPath = Op(p1, p2, op)) { | |
| canvas->drawPath(*opPath, paint); | |
| } | |
| canvas->translate(60, 0); | |
| // Do raster version of op | |
| { | |
| auto blend = op_blend_mode(op); | |
| // Create a layer. We will use blending to build a mask of the shape we want here. | |
| // Note that we're always going to get a SrcOver blend of the final shape when this | |
| // layer is restored. The math doesn't work out for most blend modes, because we're | |
| // turning the coverage of the resulting shape into the layer's alpha. | |
| canvas->saveLayer(SkRect::MakeWH(50, 50), nullptr); | |
| // We reuse this paint to apply various blend modes: | |
| SkPaint p; | |
| p.setAntiAlias(true); | |
| // Draw the first shape, using SrcOver. This fills the layer with a mask of that path: | |
| p.setBlendMode(SkBlendMode::kSrcOver); | |
| canvas->drawPath(p1, p); | |
| // Based on the PathOp we're emulating, we set a specific blend mode, and then fill | |
| // either the second path -- or its inverse. | |
| p.setBlendMode(blend.fMode); | |
| canvas->drawPath(blend.fInverse ? p2inv : p2, p); | |
| // The layer's alpha channel now contains a mask of the desired shape. Cover the entire | |
| // rectangle with whatever paint we ACTUALLY want to draw (eg, blue), using kSrcIn. | |
| // This will only draw where the mask was present: | |
| p.setBlendMode(SkBlendMode::kSrcIn); | |
| p.setColor(SK_ColorBLUE); | |
| canvas->drawPaint(p); | |
| canvas->restore(); | |
| } | |
| canvas->restore(); | |
| canvas->translate(0, 60); | |
| } | |
| } |