From c4dbc3758f06700640d0c3f33d6c72f0f6938bc8 Mon Sep 17 00:00:00 2001 From: Stephen White Date: Wed, 22 May 2019 10:50:14 -0400 Subject: [PATCH] GrTessellator: improved straight skeleton implementation. This patch improves the straight skeleton implementation used by GrTessellator to collapse overlap regions in the alpha gradient border. The resulting quality improvement can be seen in the "thinconcavepaths" GM, for example, where the coverage values of the "thin right angle", "thin rect and triangle" and "skinny snake" are now lower and match the raster backend more closely. It also improves correctness, such as on the linked Chromium bugs below. Previously, the straight skeleton was performed using the same Vertex and Edge classes used for tessellation, but this led to fragility in maintaining the connectivity and ordering required by those classes. Instead, that functionality has been moved to new SSEdge and SSVertex classes. Their construction results in alternating SSVertex and SSEdges around the boundary of the each overlap region, shrunk by one SSEdge as the boundary collapses. Applying events may now also create further events (chained events), as intersections between newly-adjacent bisectors change the structure of the skeleton. This is always calculated from the bisectors of original boundary, not the shrunk boundary of the straight skeleton, which is why each SSEdge points at its originating Edge. If the edges are parallel or near-parallel, the bisector may be infinite. This is handled by a new flavour of create_event(), which does a Line/Line intersection (rather than an Edge/Edge intersection) to find the intersection between the infinite bisector and an adjacent bisector. Several ancillary bugs were fixed: the priority queue used to represent edge events was sorting the inner edges incorrectly. These need to be sorted in descending not ascending order of coverage. Its implementation was moved from Skia's TDPQueue to std::priority_queue(), which is more flexible in specifying a comparator. check_for_intersection()'s partner synthesis code was moved into a new function, compute_bisector(), also used by the chained skeleton events code. Degenerate edges are now removed during the simplify_boundary() pass. They were previously detected but ignored, causing incorrect inner and outer tangents to be computed in stroke_boundary(). An fSynthetic flag was added to Vertex, in order to detect vertices which cannot be moved by an edge collapse event (e.g., intersections with non-boundary edges, merged vertices, straight skeleton vertices). More raw implementation notes: Connect straight skeleton vertices as we find them, so we don't have to use partnering (ss_connect()). Only disconnect edges which are still alive after event application. Add a check for near-parallel lines in compute_bisector(). Don't use edge type to determine direction to offset for bisectors. The winding should already include this information. Move Event ownership to SSEdge. If we're down to the last two edges in a skeleton, don't check for events. Add concave_arc_and_circle GM. Add a collapsepaths GM. Bug: 941429, 913349, 929915, 945853 Change-Id: Ib89e231d0e8611f8735fd3592db6391da096369d Reviewed-on: https://skia-review.googlesource.com/c/skia/+/215094 Commit-Queue: Stephen White Reviewed-by: Robert Phillips --- gm/collapsepaths.cpp | 161 +++++++++++++++++ gm/crbug_913349.cpp | 28 +++ gn/gm.gni | 2 + src/gpu/GrTessellator.cpp | 369 +++++++++++++++++++++++++------------- 4 files changed, 434 insertions(+), 126 deletions(-) create mode 100644 gm/collapsepaths.cpp create mode 100644 gm/crbug_913349.cpp diff --git a/gm/collapsepaths.cpp b/gm/collapsepaths.cpp new file mode 100644 index 0000000000..22ddf9bcbf --- /dev/null +++ b/gm/collapsepaths.cpp @@ -0,0 +1,161 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkPath.h" + +namespace { + +void test_collapse1(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + canvas->translate(0, 0); + path.moveTo( 652.830078125, 673.9365234375); + path.lineTo( 479.50152587890625, 213.412628173828125); + path.lineTo( 511.840545654296875, 209.1551055908203125); + path.lineTo( 528.14959716796875, 208.6212158203125); + path.moveTo( 370.50653076171875, 73.684051513671875); + path.lineTo( 525.02093505859375, 208.6413726806640625); + path.lineTo( 478.403564453125, 213.5998992919921875); + path.setFillType(SkPath::FillType::kEvenOdd_FillType); + canvas->drawPath(path, paint); +} + +void test_collapse2(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo( 492.781982421875, 508.7139892578125); + path.lineTo( 361.946746826171875, 161.0923004150390625); + path.lineTo( 386.357513427734375, 157.8785552978515625); + path.lineTo( 398.668212890625, 157.475555419921875); + path.moveTo( 279.673004150390625, 55.619640350341796875); + path.lineTo( 396.30657958984375, 157.4907684326171875); + path.lineTo( 361.117950439453125, 161.2336578369140625); + canvas->drawPath(path, paint); +} + +void test_collapse3(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo(31.9730987548828125, 69.4149169921875); + path.lineTo(36.630767822265625, 67.66190338134765625); + path.lineTo(51.1498870849609375, 64.2765045166015625); + path.moveTo(52.94580078125, 64.05560302734375); + path.lineTo(38.9994354248046875, 66.8980712890625); + path.lineTo(32.229583740234375, 69.31696319580078125); + path.lineTo(12.99810791015625, 22.4723663330078125); + canvas->drawPath(path, paint); +} + +void test_collapse4(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo( 122.66265869140625, 77.81488800048828125); + path.lineTo( 161.983642578125, 128.557952880859375); + path.lineTo(22.599969863891601562, 76.61859893798828125); + path.lineTo(18.03154754638671875, 76.055633544921875); + path.lineTo(15.40312957763671875, 75.7647247314453125); + path.lineTo(18.572841644287109375, 75.2251129150390625); + path.lineTo(20.895002365112304688, 73.7937774658203125); + canvas->drawPath(path, paint); +} + +void test_collapse5(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo(52.659847259521484375, 782.0546875); + path.lineTo(136.6915130615234375, 690.18011474609375); + path.lineTo( 392.147796630859375, 554.6090087890625); + path.lineTo( 516.51470947265625, 534.44134521484375); + path.moveTo(154.6182708740234375, 188.230926513671875); + path.lineTo( 430.242095947265625, 546.76605224609375); + path.lineTo( 373.1005859375, 559.0906982421875); + path.setFillType(SkPath::FillType::kEvenOdd_FillType); + canvas->drawPath(path, paint); +} + +void test_collapse6(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo(13.314494132995605469, 197.7343902587890625); + path.lineTo(34.56102752685546875, 174.5048675537109375); + path.lineTo(99.15048980712890625, 140.22711181640625); + path.lineTo( 130.595367431640625, 135.1279296875); + path.moveTo(39.09362030029296875, 47.59223175048828125); + path.lineTo(108.7822418212890625, 138.244110107421875); + path.lineTo(94.33460235595703125, 141.360260009765625); + path.setFillType(SkPath::FillType::kEvenOdd_FillType); + canvas->drawPath(path, paint); +} + +void test_collapse7(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo(13.737141609191894531, 204.0111541748046875); + path.lineTo( 35.658111572265625, 180.04425048828125); + path.lineTo(102.2978668212890625, 144.67840576171875); + path.lineTo( 134.74090576171875, 139.4173583984375); + path.moveTo(40.33458709716796875, 49.10297393798828125); + path.lineTo(112.2353668212890625, 142.6324462890625); + path.lineTo(97.32910919189453125, 145.8475189208984375); + path.setFillType(SkPath::FillType::kEvenOdd_FillType); + canvas->drawPath(path, paint); +} + +void test_collapse8(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo( 11.75, 174.50); + path.lineTo( 30.50, 154.00); + path.lineTo( 87.50, 123.75); + path.lineTo(115.25, 119.25); + path.moveTo( 34.50, 42.00); + path.lineTo( 96.00, 122.00); + path.lineTo( 83.25, 124.75); + path.setFillType(SkPath::FillType::kEvenOdd_FillType); + canvas->drawPath(path, paint); +} + +void test_collapse9(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo(SkDoubleToScalar( 13.25), SkDoubleToScalar(197.75)); + path.lineTo(SkDoubleToScalar( 34.75), SkDoubleToScalar(174.75)); + path.lineTo(SkDoubleToScalar( 99.0364), SkDoubleToScalar(140.364)); + path.lineTo(SkDoubleToScalar( 99.25), SkDoubleToScalar(140.25)); + path.lineTo(SkDoubleToScalar(100.167), SkDoubleToScalar(140.096)); + path.lineTo(SkDoubleToScalar(130.50), SkDoubleToScalar(135.00)); + path.moveTo(SkDoubleToScalar( 39.25), SkDoubleToScalar( 47.50)); + path.lineTo(SkDoubleToScalar(100.167), SkDoubleToScalar(140.096)); + path.lineTo(SkDoubleToScalar( 99.0364), SkDoubleToScalar(140.364)); + path.lineTo(SkDoubleToScalar( 94.25), SkDoubleToScalar(141.50)); + path.setFillType(SkPath::FillType::kEvenOdd_FillType); + canvas->drawPath(path, paint); +} + +// This one is a thin inverted 'v', but the edges should not disappear at any point. +void test_collapse10(SkCanvas* canvas, const SkPaint& paint) { + SkPath path; + path.moveTo( 5.5, 36.0); + path.lineTo(47.5, 5.0); + path.lineTo(90.0, 36.0); + path.lineTo(88.5, 36.0); + path.lineTo(47.5, 6.0); + path.lineTo( 7.0, 36.0); + canvas->drawPath(path, paint); +} + +}; + +DEF_SIMPLE_GM(collapsepaths, canvas, 500, 600) { + SkPaint paint; + + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + test_collapse1(canvas, paint); + test_collapse2(canvas, paint); + test_collapse3(canvas, paint); + test_collapse4(canvas, paint); + test_collapse5(canvas, paint); + test_collapse6(canvas, paint); + test_collapse7(canvas, paint); + test_collapse8(canvas, paint); + test_collapse9(canvas, paint); + test_collapse10(canvas, paint); +} diff --git a/gm/crbug_913349.cpp b/gm/crbug_913349.cpp new file mode 100644 index 0000000000..40c8749043 --- /dev/null +++ b/gm/crbug_913349.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2019 Google Inc. + * + * 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/SkCanvas.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" + +DEF_SIMPLE_GM(crbug_913349, canvas, 500, 600) { + SkPaint paint; + + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + + // This is a reduction from crbug.com/913349 to 5 verts. + SkPath path; + path.moveTo( 349.5, 225.75); + path.lineTo( 96.5, 74); + path.lineTo( 500.50, 226); + path.lineTo( 350, 226); + path.lineTo( 350, 224); + + canvas->drawPath(path, paint); +} diff --git a/gn/gm.gni b/gn/gm.gni index 02d1f862dd..7348e8b9b3 100644 --- a/gn/gm.gni +++ b/gn/gm.gni @@ -76,6 +76,7 @@ gm_sources = [ "$_gm/clipdrawdraw.cpp", "$_gm/clippedbitmapshaders.cpp", "$_gm/clockwise.cpp", + "$_gm/collapsepaths.cpp", "$_gm/color4f.cpp", "$_gm/coloremoji.cpp", "$_gm/coloremoji_blendmodes.cpp", @@ -107,6 +108,7 @@ gm_sources = [ "$_gm/crbug_892988.cpp", "$_gm/crbug_899512.cpp", "$_gm/crbug_905548.cpp", + "$_gm/crbug_913349.cpp", "$_gm/crbug_918512.cpp", "$_gm/crbug_938592.cpp", "$_gm/crbug_946965.cpp", diff --git a/src/gpu/GrTessellator.cpp b/src/gpu/GrTessellator.cpp index 1ceed08429..0a0cef9114 100644 --- a/src/gpu/GrTessellator.cpp +++ b/src/gpu/GrTessellator.cpp @@ -15,10 +15,11 @@ #include "src/core/SkArenaAlloc.h" #include "src/core/SkGeometry.h" #include "src/core/SkPointPriv.h" -#include "src/core/SkTDPQueue.h" #include #include +#include +#include #include /* @@ -153,6 +154,7 @@ struct Vertex { , fLeftEnclosingEdge(nullptr), fRightEnclosingEdge(nullptr) , fPartner(nullptr) , fAlpha(alpha) + , fSynthetic(false) #if LOGGING_ENABLED , fID (-1.0f) #endif @@ -168,6 +170,7 @@ struct Vertex { Edge* fRightEnclosingEdge; // Nearest edge in the AEL right of this vertex. Vertex* fPartner; // Corresponding inner or outer vertex (for AA). uint8_t fAlpha; + bool fSynthetic; // Is this a synthetic vertex? #if LOGGING_ENABLED float fID; // Identifier used for logging. #endif @@ -352,12 +355,10 @@ struct Edge { , fNextEdgeBelow(nullptr) , fLeftPoly(nullptr) , fRightPoly(nullptr) - , fEvent(nullptr) , fLeftPolyPrev(nullptr) , fLeftPolyNext(nullptr) , fRightPolyPrev(nullptr) , fRightPolyNext(nullptr) - , fOverlap(false) , fUsedInLeftPoly(false) , fUsedInRightPoly(false) , fLine(top, bottom) { @@ -374,12 +375,10 @@ struct Edge { Edge* fNextEdgeBelow; // " Poly* fLeftPoly; // The Poly to the left of this edge, if any. Poly* fRightPoly; // The Poly to the right of this edge, if any. - Event* fEvent; Edge* fLeftPolyPrev; Edge* fLeftPolyNext; Edge* fRightPolyPrev; Edge* fRightPolyNext; - bool fOverlap; // True if there's an overlap region adjacent to this edge. bool fUsedInLeftPoly; bool fUsedInRightPoly; Line fLine; @@ -436,6 +435,28 @@ struct Edge { } }; +struct SSEdge; + +struct SSVertex { + SSVertex(Vertex* v) : fVertex(v), fPrev(nullptr), fNext(nullptr) {} + Vertex* fVertex; + SSEdge* fPrev; + SSEdge* fNext; +}; + +struct SSEdge { + SSEdge(Edge* edge, SSVertex* prev, SSVertex* next) + : fEdge(edge), fEvent(nullptr), fPrev(prev), fNext(next) { + } + Edge* fEdge; + Event* fEvent; + SSVertex* fPrev; + SSVertex* fNext; +}; + +typedef std::unordered_map SSVertexMap; +typedef std::vector SSEdgeList; + struct EdgeList { EdgeList() : fHead(nullptr), fTail(nullptr) {} Edge* fHead; @@ -465,36 +486,70 @@ struct EdgeList { } }; +struct EventList; + struct Event { - Event(Edge* edge, bool isOuterBoundary, const SkPoint& point, uint8_t alpha) - : fEdge(edge), fIsOuterBoundary(isOuterBoundary), fPoint(point), fAlpha(alpha) - , fPrev(nullptr), fNext(nullptr) { + Event(SSEdge* edge, const SkPoint& point, uint8_t alpha) + : fEdge(edge), fPoint(point), fAlpha(alpha) { } - Edge* fEdge; - bool fIsOuterBoundary; + SSEdge* fEdge; SkPoint fPoint; uint8_t fAlpha; - Event* fPrev; - Event* fNext; - void apply(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc); + void apply(VertexList* mesh, Comparator& c, EventList* events, SkArenaAlloc& alloc); }; -bool compare(Event* const& e1, Event* const& e2) { - return e1->fAlpha > e2->fAlpha; -} +struct EventComparator { + enum class Op { kLessThan, kGreaterThan }; + EventComparator(Op op) : fOp(op) {} + bool operator() (Event* const &e1, Event* const &e2) { + return fOp == Op::kLessThan ? e1->fAlpha < e2->fAlpha + : e1->fAlpha > e2->fAlpha; + } + Op fOp; +}; -struct EventList : public SkTDPQueue {}; +typedef std::priority_queue, EventComparator> EventPQ; -void create_event(Edge* e, bool isOuterBoundary, EventList* events, SkArenaAlloc& alloc) { - Edge bisector1(e->fTop, e->fTop->fPartner, 1, Edge::Type::kConnector); - Edge bisector2(e->fBottom, e->fBottom->fPartner, 1, Edge::Type::kConnector); +struct EventList : EventPQ { + EventList(EventComparator comparison) : EventPQ(comparison) { + } +}; + +void create_event(SSEdge* e, EventList* events, SkArenaAlloc& alloc) { + Vertex* prev = e->fPrev->fVertex; + Vertex* next = e->fNext->fVertex; + if (prev == next || !prev->fPartner || !next->fPartner) { + return; + } + Edge bisector1(prev, prev->fPartner, 1, Edge::Type::kConnector); + Edge bisector2(next, next->fPartner, 1, Edge::Type::kConnector); SkPoint p; uint8_t alpha; if (bisector1.intersect(bisector2, &p, &alpha)) { - LOG("found overlap edge %g -> %g, will collapse to %g,%g alpha %d\n", - e->fTop->fID, e->fBottom->fID, p.fX, p.fY, alpha); - e->fEvent = alloc.make(e, isOuterBoundary, p, alpha); - events->insert(e->fEvent); + LOG("found edge event for %g, %g (original %g -> %g), will collapse to %g,%g alpha %d\n", + prev->fID, next->fID, e->fEdge->fTop->fID, e->fEdge->fBottom->fID, p.fX, p.fY, alpha); + e->fEvent = alloc.make(e, p, alpha); + events->push(e->fEvent); + } +} + +void create_event(SSEdge* edge, Vertex* v, SSEdge* other, Vertex* dest, EventList* events, + Comparator& c, SkArenaAlloc& alloc) { + if (!v->fPartner) { + return; + } + Line line = edge->fEdge->fLine; + line.fC = -(dest->fPoint.fX * line.fA + dest->fPoint.fY * line.fB); + Edge bisector(v, v->fPartner, 1, Edge::Type::kConnector); + SkPoint p; + uint8_t alpha = dest->fAlpha; + if (line.intersect(bisector.fLine, &p) && !c.sweep_lt(p, edge->fEdge->fTop->fPoint) && + c.sweep_lt(p, edge->fEdge->fBottom->fPoint)) { + LOG("found p edge event for %g, %g (original %g -> %g), will collapse to %g,%g alpha %d\n", + dest->fID, v->fID, edge->fEdge->fTop->fID, edge->fEdge->fBottom->fID, p.fX, p.fY, + alpha); + edge->fEvent = alloc.make(edge, p, alpha); + events->push(edge->fEvent); } } @@ -1164,6 +1219,7 @@ void merge_vertices(Vertex* src, Vertex* dst, VertexList* mesh, Comparator& c, set_top(edge, dst, nullptr, nullptr, c); } mesh->remove(src); + dst->fSynthetic = true; } Vertex* create_sorted_vertex(const SkPoint& p, uint8_t alpha, VertexList* mesh, @@ -1217,6 +1273,25 @@ SkPoint clamp(SkPoint p, SkPoint min, SkPoint max, Comparator& c) { } } +void compute_bisector(Edge* edge1, Edge* edge2, Vertex* v, SkArenaAlloc& alloc) { + Line line1 = edge1->fLine; + Line line2 = edge2->fLine; + line1.normalize(); + line2.normalize(); + double cosAngle = line1.fA * line2.fA + line1.fB * line2.fB; + if (cosAngle > 0.999) { + return; + } + line1.fC += edge1->fWinding > 0 ? -1 : 1; + line2.fC += edge2->fWinding > 0 ? -1 : 1; + SkPoint p; + if (line1.intersect(line2, &p)) { + uint8_t alpha = edge1->fType == Edge::Type::kOuter ? 255 : 0; + v->fPartner = alloc.make(p, alpha); + LOG("computed bisector (%g,%g) alpha %d for vertex %g\n", p.fX, p.fY, alpha, v->fID); + } +} + bool check_for_intersection(Edge* left, Edge* right, EdgeList* activeEdges, Vertex** current, VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { if (!left || !right) { @@ -1250,17 +1325,8 @@ bool check_for_intersection(Edge* left, Edge* right, EdgeList* activeEdges, Vert } else { v = create_sorted_vertex(p, alpha, mesh, top, c, alloc); if (left->fTop->fPartner) { - Line line1 = left->fLine; - Line line2 = right->fLine; - int dir = left->fType == Edge::Type::kOuter ? -1 : 1; - line1.fC += sqrt(left->fLine.magSq()) * (left->fWinding > 0 ? 1 : -1) * dir; - line2.fC += sqrt(right->fLine.magSq()) * (right->fWinding > 0 ? 1 : -1) * dir; - SkPoint p; - if (line1.intersect(line2, &p)) { - LOG("synthesizing partner (%g,%g) for intersection vertex %g\n", - p.fX, p.fY, v->fID); - v->fPartner = alloc.make(p, 255 - v->fAlpha); - } + v->fSynthetic = true; + compute_bisector(left, right, v, alloc); } } rewind(activeEdges, current, top ? top : v, c); @@ -1433,6 +1499,21 @@ void dump_mesh(const VertexList& mesh) { #endif } +void dump_skel(const SSEdgeList& ssEdges) { +#if LOGGING_ENABLED + LOG("skeleton:\n"); + for (SSEdge* edge : ssEdges) { + if (edge->fEdge) { + LOG("skel edge %g -> %g (original %g -> %g)\n", + edge->fPrev->fVertex->fID, + edge->fNext->fVertex->fID, + edge->fEdge->fTop->fID, + edge->fEdge->fBottom->fID); + } + } +#endif +} + #ifdef SK_DEBUG void validate_edge_pair(Edge* left, Edge* right, Comparator& c) { if (!left || !right) { @@ -1470,12 +1551,16 @@ void validate_edge_list(EdgeList* edges, Comparator& c) { // Stage 4: Simplify the mesh by inserting new vertices at intersecting edges. +bool connected(Vertex* v) { + return v->fFirstEdgeAbove || v->fFirstEdgeBelow; +} + bool simplify(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { LOG("simplifying complex polygons\n"); EdgeList activeEdges; bool found = false; for (Vertex* v = mesh->fHead; v != nullptr; v = v->fNext) { - if (!v->fFirstEdgeAbove && !v->fFirstEdgeBelow) { + if (!connected(v)) { continue; } Edge* leftEnclosingEdge; @@ -1532,7 +1617,7 @@ Poly* tessellate(const VertexList& vertices, SkArenaAlloc& alloc) { EdgeList activeEdges; Poly* polys = nullptr; for (Vertex* v = vertices.fHead; v != nullptr; v = v->fNext) { - if (!v->fFirstEdgeAbove && !v->fFirstEdgeBelow) { + if (!connected(v)) { continue; } #if LOGGING_ENABLED @@ -1639,7 +1724,7 @@ void remove_non_boundary_edges(const VertexList& mesh, SkPath::FillType fillType LOG("removing non-boundary edges\n"); EdgeList activeEdges; for (Vertex* v = mesh.fHead; v != nullptr; v = v->fNext) { - if (!v->fFirstEdgeAbove && !v->fFirstEdgeBelow) { + if (!connected(v)) { continue; } Edge* leftEnclosingEdge; @@ -1674,31 +1759,6 @@ void get_edge_normal(const Edge* e, SkVector* normal) { SkDoubleToScalar(e->fLine.fB)); } -void reconnect(Edge* edge, Vertex* src, Vertex* dst, Comparator& c) { - disconnect(edge); - if (src == edge->fTop) { - edge->fTop = dst; - } else { - SkASSERT(src == edge->fBottom); - edge->fBottom = dst; - } - if (edge->fEvent) { - edge->fEvent->fEdge = nullptr; - } - if (edge->fTop == edge->fBottom) { - return; - } - if (c.sweep_lt(edge->fBottom->fPoint, edge->fTop->fPoint)) { - using std::swap; - swap(edge->fTop, edge->fBottom); - edge->fWinding *= -1; - } - edge->recompute(); - insert_edge_below(edge, edge->fTop, c); - insert_edge_above(edge, edge->fBottom, c); - merge_collinear_edges(edge, nullptr, nullptr, c); -} - // Stage 5c: detect and remove "pointy" vertices whose edge normals point in opposite directions // and whose adjacent vertices are less than a quarter pixel from an edge. These are guaranteed to // invert on stroking. @@ -1715,7 +1775,15 @@ void simplify_boundary(EdgeList* boundary, Comparator& c, SkArenaAlloc& alloc) { SkVector normal; get_edge_normal(e, &normal); constexpr double kQuarterPixelSq = 0.25f * 0.25f; - if (prev != next && prevNormal.dot(normal) < 0.0 && + if (prev == next) { + remove_edge(prevEdge, boundary); + remove_edge(e, boundary); + prevEdge = boundary->fTail; + e = boundary->fHead; + if (prevEdge) { + get_edge_normal(prevEdge, &prevNormal); + } + } else if (prevNormal.dot(normal) < 0.0 && (distPrev * distPrev <= kQuarterPixelSq || distNext * distNext <= kQuarterPixelSq)) { Edge* join = new_edge(prev, next, Edge::Type::kInner, c, alloc); if (prev->fPoint != next->fPoint) { @@ -1741,56 +1809,69 @@ void simplify_boundary(EdgeList* boundary, Comparator& c, SkArenaAlloc& alloc) { } } -void reconnect_all_overlap_edges(Vertex* src, Vertex* dst, Edge* current, Comparator& c) { - if (src->fPartner) { - src->fPartner->fPartner = dst; +void ss_connect(Vertex* v, Vertex* dest, Comparator& c, SkArenaAlloc& alloc) { + if (v == dest) { + return; } - for (Edge* e = src->fFirstEdgeAbove; e; ) { - Edge* next = e->fNextEdgeAbove; - if (e->fOverlap && e != current) { - reconnect(e, src, dst, c); - } - e = next; - } - for (Edge* e = src->fFirstEdgeBelow; e; ) { - Edge* next = e->fNextEdgeBelow; - if (e->fOverlap && e != current) { - reconnect(e, src, dst, c); - } - e = next; + LOG("ss_connecting vertex %g to vertex %g\n", v->fID, dest->fID); + if (v->fSynthetic) { + connect(v, dest, Edge::Type::kConnector, c, alloc, 0); + } else if (v->fPartner) { + LOG("setting %g's partner to %g ", v->fPartner->fID, dest->fID); + LOG("and %g's partner to null\n", v->fID); + v->fPartner->fPartner = dest; + v->fPartner = nullptr; } } -void Event::apply(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { - if (!fEdge || !fEdge->fTop || !fEdge->fBottom) { +void Event::apply(VertexList* mesh, Comparator& c, EventList* events, SkArenaAlloc& alloc) { + if (!fEdge) { return; } - Vertex* top = fEdge->fTop; - Vertex* bottom = fEdge->fBottom; - Vertex* dest = create_sorted_vertex(fPoint, fAlpha, mesh, fEdge->fTop, c, alloc); - LOG("collapsing edge %g -> %g to %g (%g, %g) alpha %d\n", - top->fID, bottom->fID, dest->fID, fPoint.fX, fPoint.fY, fAlpha); - reconnect_all_overlap_edges(top, dest, fEdge, c); - reconnect_all_overlap_edges(bottom, dest, fEdge, c); - - // Since the destination has multiple partners, give it none. - dest->fPartner = nullptr; - - // Disconnect all collapsed edges except outer boundaries. - // Those are required to preserve shape coverage and winding correctness. - if (!fIsOuterBoundary) { - disconnect(fEdge); - } else { - LOG("edge %g -> %g is outer boundary; not disconnecting.\n", - fEdge->fTop->fID, fEdge->fBottom->fID); - fEdge->fWinding = fEdge->fWinding >= 0 ? 1 : -1; + Vertex* prev = fEdge->fPrev->fVertex; + Vertex* next = fEdge->fNext->fVertex; + SSEdge* prevEdge = fEdge->fPrev->fPrev; + SSEdge* nextEdge = fEdge->fNext->fNext; + if (!prevEdge || !nextEdge || !prevEdge->fEdge || !nextEdge->fEdge) { + return; } + Vertex* dest = create_sorted_vertex(fPoint, fAlpha, mesh, prev, c, alloc); + dest->fSynthetic = true; + SSVertex* ssv = alloc.make(dest); + LOG("collapsing %g, %g (original edge %g -> %g) to %g (%g, %g) alpha %d\n", + prev->fID, next->fID, fEdge->fEdge->fTop->fID, fEdge->fEdge->fBottom->fID, + dest->fID, fPoint.fX, fPoint.fY, fAlpha); + fEdge->fEdge = nullptr; - // If top still has some connected edges, set its partner to dest. - top->fPartner = top->fFirstEdgeAbove || top->fFirstEdgeBelow ? dest : nullptr; + ss_connect(prev, dest, c, alloc); + ss_connect(next, dest, c, alloc); - // If bottom still has some connected edges, set its partner to dest. - bottom->fPartner = bottom->fFirstEdgeAbove || bottom->fFirstEdgeBelow ? dest : nullptr; + prevEdge->fNext = nextEdge->fPrev = ssv; + ssv->fPrev = prevEdge; + ssv->fNext = nextEdge; + if (!prevEdge->fEdge || !nextEdge->fEdge) { + return; + } + if (prevEdge->fEvent) { + prevEdge->fEvent->fEdge = nullptr; + } + if (nextEdge->fEvent) { + nextEdge->fEvent->fEdge = nullptr; + } + if (prevEdge->fPrev == nextEdge->fNext) { + ss_connect(prevEdge->fPrev->fVertex, dest, c, alloc); + prevEdge->fEdge = nextEdge->fEdge = nullptr; + } else { + compute_bisector(prevEdge->fEdge, nextEdge->fEdge, dest, alloc); + SkASSERT(prevEdge != fEdge && nextEdge != fEdge); + if (dest->fPartner) { + create_event(prevEdge, events, alloc); + create_event(nextEdge, events, alloc); + } else { + create_event(prevEdge, prevEdge->fPrev->fVertex, nextEdge, dest, events, c, alloc); + create_event(nextEdge, nextEdge->fNext->fVertex, prevEdge, dest, events, c, alloc); + } + } } bool is_overlap_edge(Edge* e) { @@ -1805,51 +1886,82 @@ bool is_overlap_edge(Edge* e) { // This is a stripped-down version of tessellate() which computes edges which // join two filled regions, which represent overlap regions, and collapses them. -bool collapse_overlap_regions(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { +bool collapse_overlap_regions(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc, + EventComparator comp) { LOG("\nfinding overlap regions\n"); EdgeList activeEdges; - EventList events; + EventList events(comp); + SSVertexMap ssVertices; + SSEdgeList ssEdges; for (Vertex* v = mesh->fHead; v != nullptr; v = v->fNext) { - if (!v->fFirstEdgeAbove && !v->fFirstEdgeBelow) { + if (!connected(v)) { continue; } Edge* leftEnclosingEdge; Edge* rightEnclosingEdge; find_enclosing_edges(v, &activeEdges, &leftEnclosingEdge, &rightEnclosingEdge); - for (Edge* e = v->fLastEdgeAbove; e; e = e->fPrevEdgeAbove) { + for (Edge* e = v->fLastEdgeAbove; e && e != leftEnclosingEdge;) { Edge* prev = e->fPrevEdgeAbove ? e->fPrevEdgeAbove : leftEnclosingEdge; remove_edge(e, &activeEdges); + bool leftOverlap = prev && is_overlap_edge(prev); + bool rightOverlap = is_overlap_edge(e); + bool isOuterBoundary = e->fType == Edge::Type::kOuter && + (!prev || prev->fWinding == 0 || e->fWinding == 0); if (prev) { e->fWinding -= prev->fWinding; } + if (leftOverlap && rightOverlap) { + LOG("found interior overlap edge %g -> %g, disconnecting\n", + e->fTop->fID, e->fBottom->fID); + disconnect(e); + } else if (leftOverlap || rightOverlap) { + LOG("found overlap edge %g -> %g%s\n", e->fTop->fID, e->fBottom->fID, + isOuterBoundary ? ", is outer boundary" : ""); + Vertex* prevVertex = e->fWinding < 0 ? e->fBottom : e->fTop; + Vertex* nextVertex = e->fWinding < 0 ? e->fTop : e->fBottom; + SSVertex* ssPrev = ssVertices[prevVertex]; + if (!ssPrev) { + ssPrev = ssVertices[prevVertex] = alloc.make(prevVertex); + } + SSVertex* ssNext = ssVertices[nextVertex]; + if (!ssNext) { + ssNext = ssVertices[nextVertex] = alloc.make(nextVertex); + } + SSEdge* ssEdge = alloc.make(e, ssPrev, ssNext); + ssEdges.push_back(ssEdge); +// SkASSERT(!ssPrev->fNext && !ssNext->fPrev); + ssPrev->fNext = ssNext->fPrev = ssEdge; + create_event(ssEdge, &events, alloc); + if (!isOuterBoundary) { + disconnect(e); + } + } + e = prev; } Edge* prev = leftEnclosingEdge; for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { if (prev) { e->fWinding += prev->fWinding; - e->fOverlap = e->fOverlap || is_overlap_edge(prev); - } - e->fOverlap = e->fOverlap || is_overlap_edge(e); - if (e->fOverlap) { - // If this edge borders a zero-winding area, it's a boundary; don't disconnect it. - bool isOuterBoundary = e->fType == Edge::Type::kOuter && - (!prev || prev->fWinding == 0 || e->fWinding == 0); - create_event(e, isOuterBoundary, &events, alloc); } insert_edge(e, prev, &activeEdges); prev = e; } } + bool complex = events.size() > 0; + LOG("\ncollapsing overlap regions\n"); - if (events.count() == 0) { - return false; - } - while (events.count() > 0) { - Event* event = events.peek(); + while (events.size() > 0) { + Event* event = events.top(); events.pop(); - event->apply(mesh, c, alloc); + event->apply(mesh, c, &events, alloc); } - return true; + dump_skel(ssEdges); + for (SSEdge* edge : ssEdges) { + if (Edge* e = edge->fEdge) { + connect(edge->fPrev->fVertex, edge->fNext->fVertex, e->fType, c, alloc, 0); + } + } + return complex; } bool inversion(Vertex* prev, Vertex* next, Edge* origEdge, Comparator& c) { @@ -2118,6 +2230,8 @@ Poly* contours_to_polys(VertexList* contours, int contourCnt, SkPath::FillType f sort_mesh(&mesh, c, alloc); merge_coincident_vertices(&mesh, c, alloc); simplify(&mesh, c, alloc); + LOG("\nsimplified mesh:\n"); + dump_mesh(mesh); if (antialias) { VertexList innerMesh; extract_boundaries(mesh, &innerMesh, outerMesh, fillType, c, alloc); @@ -2131,8 +2245,10 @@ Poly* contours_to_polys(VertexList* contours, int contourCnt, SkPath::FillType f dump_mesh(innerMesh); LOG("\nouter mesh before:\n"); dump_mesh(*outerMesh); - was_complex = collapse_overlap_regions(&innerMesh, c, alloc) || was_complex; - was_complex = collapse_overlap_regions(outerMesh, c, alloc) || was_complex; + EventComparator eventLT(EventComparator::Op::kLessThan); + EventComparator eventGT(EventComparator::Op::kGreaterThan); + was_complex = collapse_overlap_regions(&innerMesh, c, alloc, eventLT) || was_complex; + was_complex = collapse_overlap_regions(outerMesh, c, alloc, eventGT) || was_complex; if (was_complex) { LOG("found complex mesh; taking slow path\n"); VertexList aaMesh; @@ -2145,6 +2261,7 @@ Poly* contours_to_polys(VertexList* contours, int contourCnt, SkPath::FillType f sorted_merge(&innerMesh, outerMesh, &aaMesh, c); merge_coincident_vertices(&aaMesh, c, alloc); simplify(&aaMesh, c, alloc); + LOG("combined and simplified mesh:\n"); dump_mesh(aaMesh); outerMesh->fHead = outerMesh->fTail = nullptr; return tessellate(aaMesh, alloc);