xref: /aosp_15_r20/external/skia/src/pathops/SkOpBuilder.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2014 Google Inc.
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 "include/core/SkPath.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathTypes.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPoint.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/pathops/SkPathOps.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTArray.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTDArray.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTo.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkArenaAlloc.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkPathEnums.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkPathPriv.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/pathops/SkOpContour.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/pathops/SkOpEdgeBuilder.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/pathops/SkOpSegment.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "src/pathops/SkOpSpan.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/pathops/SkPathOpsCommon.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "src/pathops/SkPathOpsTypes.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "src/pathops/SkPathWriter.h"
27*c8dee2aaSAndroid Build Coastguard Worker 
28*c8dee2aaSAndroid Build Coastguard Worker #include <cstdint>
29*c8dee2aaSAndroid Build Coastguard Worker 
one_contour(const SkPath & path)30*c8dee2aaSAndroid Build Coastguard Worker static bool one_contour(const SkPath& path) {
31*c8dee2aaSAndroid Build Coastguard Worker     SkSTArenaAlloc<256> allocator;
32*c8dee2aaSAndroid Build Coastguard Worker     int verbCount = path.countVerbs();
33*c8dee2aaSAndroid Build Coastguard Worker     uint8_t* verbs = (uint8_t*) allocator.makeArrayDefault<uint8_t>(verbCount);
34*c8dee2aaSAndroid Build Coastguard Worker     (void) path.getVerbs(verbs, verbCount);
35*c8dee2aaSAndroid Build Coastguard Worker     for (int index = 1; index < verbCount; ++index) {
36*c8dee2aaSAndroid Build Coastguard Worker         if (verbs[index] == SkPath::kMove_Verb) {
37*c8dee2aaSAndroid Build Coastguard Worker             return false;
38*c8dee2aaSAndroid Build Coastguard Worker         }
39*c8dee2aaSAndroid Build Coastguard Worker     }
40*c8dee2aaSAndroid Build Coastguard Worker     return true;
41*c8dee2aaSAndroid Build Coastguard Worker }
42*c8dee2aaSAndroid Build Coastguard Worker 
ReversePath(SkPath * path)43*c8dee2aaSAndroid Build Coastguard Worker void SkOpBuilder::ReversePath(SkPath* path) {
44*c8dee2aaSAndroid Build Coastguard Worker     SkPath temp;
45*c8dee2aaSAndroid Build Coastguard Worker     SkPoint lastPt;
46*c8dee2aaSAndroid Build Coastguard Worker     SkAssertResult(path->getLastPt(&lastPt));
47*c8dee2aaSAndroid Build Coastguard Worker     temp.moveTo(lastPt);
48*c8dee2aaSAndroid Build Coastguard Worker     temp.reversePathTo(*path);
49*c8dee2aaSAndroid Build Coastguard Worker     temp.close();
50*c8dee2aaSAndroid Build Coastguard Worker     *path = temp;
51*c8dee2aaSAndroid Build Coastguard Worker }
52*c8dee2aaSAndroid Build Coastguard Worker 
FixWinding(SkPath * path)53*c8dee2aaSAndroid Build Coastguard Worker bool SkOpBuilder::FixWinding(SkPath* path) {
54*c8dee2aaSAndroid Build Coastguard Worker     SkPathFillType fillType = path->getFillType();
55*c8dee2aaSAndroid Build Coastguard Worker     if (fillType == SkPathFillType::kInverseEvenOdd) {
56*c8dee2aaSAndroid Build Coastguard Worker         fillType = SkPathFillType::kInverseWinding;
57*c8dee2aaSAndroid Build Coastguard Worker     } else if (fillType == SkPathFillType::kEvenOdd) {
58*c8dee2aaSAndroid Build Coastguard Worker         fillType = SkPathFillType::kWinding;
59*c8dee2aaSAndroid Build Coastguard Worker     }
60*c8dee2aaSAndroid Build Coastguard Worker     if (one_contour(*path)) {
61*c8dee2aaSAndroid Build Coastguard Worker         SkPathFirstDirection dir = SkPathPriv::ComputeFirstDirection(*path);
62*c8dee2aaSAndroid Build Coastguard Worker         if (dir != SkPathFirstDirection::kUnknown) {
63*c8dee2aaSAndroid Build Coastguard Worker             if (dir == SkPathFirstDirection::kCW) {
64*c8dee2aaSAndroid Build Coastguard Worker                 ReversePath(path);
65*c8dee2aaSAndroid Build Coastguard Worker             }
66*c8dee2aaSAndroid Build Coastguard Worker             path->setFillType(fillType);
67*c8dee2aaSAndroid Build Coastguard Worker             return true;
68*c8dee2aaSAndroid Build Coastguard Worker         }
69*c8dee2aaSAndroid Build Coastguard Worker     }
70*c8dee2aaSAndroid Build Coastguard Worker     SkSTArenaAlloc<4096> allocator;
71*c8dee2aaSAndroid Build Coastguard Worker     SkOpContourHead contourHead;
72*c8dee2aaSAndroid Build Coastguard Worker     SkOpGlobalState globalState(&contourHead, &allocator  SkDEBUGPARAMS(false)
73*c8dee2aaSAndroid Build Coastguard Worker             SkDEBUGPARAMS(nullptr));
74*c8dee2aaSAndroid Build Coastguard Worker     SkOpEdgeBuilder builder(*path, &contourHead, &globalState);
75*c8dee2aaSAndroid Build Coastguard Worker     if (builder.unparseable() || !builder.finish()) {
76*c8dee2aaSAndroid Build Coastguard Worker         return false;
77*c8dee2aaSAndroid Build Coastguard Worker     }
78*c8dee2aaSAndroid Build Coastguard Worker     if (!contourHead.count()) {
79*c8dee2aaSAndroid Build Coastguard Worker         return true;
80*c8dee2aaSAndroid Build Coastguard Worker     }
81*c8dee2aaSAndroid Build Coastguard Worker     if (!contourHead.next()) {
82*c8dee2aaSAndroid Build Coastguard Worker         return false;
83*c8dee2aaSAndroid Build Coastguard Worker     }
84*c8dee2aaSAndroid Build Coastguard Worker     contourHead.joinAllSegments();
85*c8dee2aaSAndroid Build Coastguard Worker     contourHead.resetReverse();
86*c8dee2aaSAndroid Build Coastguard Worker     bool writePath = false;
87*c8dee2aaSAndroid Build Coastguard Worker     SkOpSpan* topSpan;
88*c8dee2aaSAndroid Build Coastguard Worker     globalState.setPhase(SkOpPhase::kFixWinding);
89*c8dee2aaSAndroid Build Coastguard Worker     while ((topSpan = FindSortableTop(&contourHead))) {
90*c8dee2aaSAndroid Build Coastguard Worker         SkOpSegment* topSegment = topSpan->segment();
91*c8dee2aaSAndroid Build Coastguard Worker         SkOpContour* topContour = topSegment->contour();
92*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(topContour->isCcw() >= 0);
93*c8dee2aaSAndroid Build Coastguard Worker #if DEBUG_WINDING
94*c8dee2aaSAndroid Build Coastguard Worker         SkDebugf("%s id=%d nested=%d ccw=%d\n",  __FUNCTION__,
95*c8dee2aaSAndroid Build Coastguard Worker                 topSegment->debugID(), globalState.nested(), topContour->isCcw());
96*c8dee2aaSAndroid Build Coastguard Worker #endif
97*c8dee2aaSAndroid Build Coastguard Worker         if ((globalState.nested() & 1) != SkToBool(topContour->isCcw())) {
98*c8dee2aaSAndroid Build Coastguard Worker             topContour->setReverse();
99*c8dee2aaSAndroid Build Coastguard Worker             writePath = true;
100*c8dee2aaSAndroid Build Coastguard Worker         }
101*c8dee2aaSAndroid Build Coastguard Worker         topContour->markAllDone();
102*c8dee2aaSAndroid Build Coastguard Worker         globalState.clearNested();
103*c8dee2aaSAndroid Build Coastguard Worker     }
104*c8dee2aaSAndroid Build Coastguard Worker     if (!writePath) {
105*c8dee2aaSAndroid Build Coastguard Worker         path->setFillType(fillType);
106*c8dee2aaSAndroid Build Coastguard Worker         return true;
107*c8dee2aaSAndroid Build Coastguard Worker     }
108*c8dee2aaSAndroid Build Coastguard Worker     SkPath empty;
109*c8dee2aaSAndroid Build Coastguard Worker     SkPathWriter woundPath(empty);
110*c8dee2aaSAndroid Build Coastguard Worker     SkOpContour* test = &contourHead;
111*c8dee2aaSAndroid Build Coastguard Worker     do {
112*c8dee2aaSAndroid Build Coastguard Worker         if (!test->count()) {
113*c8dee2aaSAndroid Build Coastguard Worker             continue;
114*c8dee2aaSAndroid Build Coastguard Worker         }
115*c8dee2aaSAndroid Build Coastguard Worker         if (test->reversed()) {
116*c8dee2aaSAndroid Build Coastguard Worker             test->toReversePath(&woundPath);
117*c8dee2aaSAndroid Build Coastguard Worker         } else {
118*c8dee2aaSAndroid Build Coastguard Worker             test->toPath(&woundPath);
119*c8dee2aaSAndroid Build Coastguard Worker         }
120*c8dee2aaSAndroid Build Coastguard Worker     } while ((test = test->next()));
121*c8dee2aaSAndroid Build Coastguard Worker     *path = *woundPath.nativePath();
122*c8dee2aaSAndroid Build Coastguard Worker     path->setFillType(fillType);
123*c8dee2aaSAndroid Build Coastguard Worker     return true;
124*c8dee2aaSAndroid Build Coastguard Worker }
125*c8dee2aaSAndroid Build Coastguard Worker 
add(const SkPath & path,SkPathOp op)126*c8dee2aaSAndroid Build Coastguard Worker void SkOpBuilder::add(const SkPath& path, SkPathOp op) {
127*c8dee2aaSAndroid Build Coastguard Worker     if (fOps.empty() && op != kUnion_SkPathOp) {
128*c8dee2aaSAndroid Build Coastguard Worker         fPathRefs.push_back() = SkPath();
129*c8dee2aaSAndroid Build Coastguard Worker         *fOps.append() = kUnion_SkPathOp;
130*c8dee2aaSAndroid Build Coastguard Worker     }
131*c8dee2aaSAndroid Build Coastguard Worker     fPathRefs.push_back() = path;
132*c8dee2aaSAndroid Build Coastguard Worker     *fOps.append() = op;
133*c8dee2aaSAndroid Build Coastguard Worker }
134*c8dee2aaSAndroid Build Coastguard Worker 
reset()135*c8dee2aaSAndroid Build Coastguard Worker void SkOpBuilder::reset() {
136*c8dee2aaSAndroid Build Coastguard Worker     fPathRefs.clear();
137*c8dee2aaSAndroid Build Coastguard Worker     fOps.reset();
138*c8dee2aaSAndroid Build Coastguard Worker }
139*c8dee2aaSAndroid Build Coastguard Worker 
140*c8dee2aaSAndroid Build Coastguard Worker /* OPTIMIZATION: Union doesn't need to be all-or-nothing. A run of three or more convex
141*c8dee2aaSAndroid Build Coastguard Worker    paths with union ops could be locally resolved and still improve over doing the
142*c8dee2aaSAndroid Build Coastguard Worker    ops one at a time. */
resolve(SkPath * result)143*c8dee2aaSAndroid Build Coastguard Worker bool SkOpBuilder::resolve(SkPath* result) {
144*c8dee2aaSAndroid Build Coastguard Worker     SkPath original = *result;
145*c8dee2aaSAndroid Build Coastguard Worker     int count = fOps.size();
146*c8dee2aaSAndroid Build Coastguard Worker     bool allUnion = true;
147*c8dee2aaSAndroid Build Coastguard Worker     SkPathFirstDirection firstDir = SkPathFirstDirection::kUnknown;
148*c8dee2aaSAndroid Build Coastguard Worker     for (int index = 0; index < count; ++index) {
149*c8dee2aaSAndroid Build Coastguard Worker         SkPath* test = &fPathRefs[index];
150*c8dee2aaSAndroid Build Coastguard Worker         if (kUnion_SkPathOp != fOps[index] || test->isInverseFillType()) {
151*c8dee2aaSAndroid Build Coastguard Worker             allUnion = false;
152*c8dee2aaSAndroid Build Coastguard Worker             break;
153*c8dee2aaSAndroid Build Coastguard Worker         }
154*c8dee2aaSAndroid Build Coastguard Worker         // If all paths are convex, track direction, reversing as needed.
155*c8dee2aaSAndroid Build Coastguard Worker         if (test->isConvex()) {
156*c8dee2aaSAndroid Build Coastguard Worker             SkPathFirstDirection dir = SkPathPriv::ComputeFirstDirection(*test);
157*c8dee2aaSAndroid Build Coastguard Worker             if (dir == SkPathFirstDirection::kUnknown) {
158*c8dee2aaSAndroid Build Coastguard Worker                 allUnion = false;
159*c8dee2aaSAndroid Build Coastguard Worker                 break;
160*c8dee2aaSAndroid Build Coastguard Worker             }
161*c8dee2aaSAndroid Build Coastguard Worker             if (firstDir == SkPathFirstDirection::kUnknown) {
162*c8dee2aaSAndroid Build Coastguard Worker                 firstDir = dir;
163*c8dee2aaSAndroid Build Coastguard Worker             } else if (firstDir != dir) {
164*c8dee2aaSAndroid Build Coastguard Worker                 ReversePath(test);
165*c8dee2aaSAndroid Build Coastguard Worker             }
166*c8dee2aaSAndroid Build Coastguard Worker             continue;
167*c8dee2aaSAndroid Build Coastguard Worker         }
168*c8dee2aaSAndroid Build Coastguard Worker         // If the path is not convex but its bounds do not intersect the others, simplify is enough.
169*c8dee2aaSAndroid Build Coastguard Worker         const SkRect& testBounds = test->getBounds();
170*c8dee2aaSAndroid Build Coastguard Worker         for (int inner = 0; inner < index; ++inner) {
171*c8dee2aaSAndroid Build Coastguard Worker             // OPTIMIZE: check to see if the contour bounds do not intersect other contour bounds?
172*c8dee2aaSAndroid Build Coastguard Worker             if (SkRect::Intersects(fPathRefs[inner].getBounds(), testBounds)) {
173*c8dee2aaSAndroid Build Coastguard Worker                 allUnion = false;
174*c8dee2aaSAndroid Build Coastguard Worker                 break;
175*c8dee2aaSAndroid Build Coastguard Worker             }
176*c8dee2aaSAndroid Build Coastguard Worker         }
177*c8dee2aaSAndroid Build Coastguard Worker     }
178*c8dee2aaSAndroid Build Coastguard Worker     if (!allUnion) {
179*c8dee2aaSAndroid Build Coastguard Worker         *result = fPathRefs[0];
180*c8dee2aaSAndroid Build Coastguard Worker         for (int index = 1; index < count; ++index) {
181*c8dee2aaSAndroid Build Coastguard Worker             if (!Op(*result, fPathRefs[index], fOps[index], result)) {
182*c8dee2aaSAndroid Build Coastguard Worker                 reset();
183*c8dee2aaSAndroid Build Coastguard Worker                 *result = original;
184*c8dee2aaSAndroid Build Coastguard Worker                 return false;
185*c8dee2aaSAndroid Build Coastguard Worker             }
186*c8dee2aaSAndroid Build Coastguard Worker         }
187*c8dee2aaSAndroid Build Coastguard Worker         reset();
188*c8dee2aaSAndroid Build Coastguard Worker         return true;
189*c8dee2aaSAndroid Build Coastguard Worker     }
190*c8dee2aaSAndroid Build Coastguard Worker     SkPath sum;
191*c8dee2aaSAndroid Build Coastguard Worker     for (int index = 0; index < count; ++index) {
192*c8dee2aaSAndroid Build Coastguard Worker         if (!Simplify(fPathRefs[index], &fPathRefs[index])) {
193*c8dee2aaSAndroid Build Coastguard Worker             reset();
194*c8dee2aaSAndroid Build Coastguard Worker             *result = original;
195*c8dee2aaSAndroid Build Coastguard Worker             return false;
196*c8dee2aaSAndroid Build Coastguard Worker         }
197*c8dee2aaSAndroid Build Coastguard Worker         if (!fPathRefs[index].isEmpty()) {
198*c8dee2aaSAndroid Build Coastguard Worker             // convert the even odd result back to winding form before accumulating it
199*c8dee2aaSAndroid Build Coastguard Worker             if (!FixWinding(&fPathRefs[index])) {
200*c8dee2aaSAndroid Build Coastguard Worker                 *result = original;
201*c8dee2aaSAndroid Build Coastguard Worker                 return false;
202*c8dee2aaSAndroid Build Coastguard Worker             }
203*c8dee2aaSAndroid Build Coastguard Worker             sum.addPath(fPathRefs[index]);
204*c8dee2aaSAndroid Build Coastguard Worker         }
205*c8dee2aaSAndroid Build Coastguard Worker     }
206*c8dee2aaSAndroid Build Coastguard Worker     reset();
207*c8dee2aaSAndroid Build Coastguard Worker     bool success = Simplify(sum, result);
208*c8dee2aaSAndroid Build Coastguard Worker     if (!success) {
209*c8dee2aaSAndroid Build Coastguard Worker         *result = original;
210*c8dee2aaSAndroid Build Coastguard Worker     }
211*c8dee2aaSAndroid Build Coastguard Worker     return success;
212*c8dee2aaSAndroid Build Coastguard Worker }
213