| /* |
| * 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/SkRect.h" |
| #include "include/pathops/SkPathOps.h" |
| #include "src/core/SkStroke.h" |
| #include "tools/ToolUtils.h" |
| |
| static SkPath cross() { |
| SkPath path; |
| path.addRect(15, 0, 35, 50); |
| path.addRect(0, 15, 50, 35); |
| return path; |
| } |
| |
| 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: |
| { |
| SkPath opPath; |
| Op(p1, p2, op, &opPath); |
| 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); |
| } |
| } |