canvas->drawPath(path, paint);
}
+// Test thin horizontal line (<1 pixel) which should give lower alpha.
+void draw_golf_club(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
+ SkPath path;
+ path.moveTo(20, 10);
+ path.lineTo(80, 10);
+ path.lineTo(80, 10 + width);
+ path.lineTo(30, 10 + width);
+ path.lineTo(30, 20);
+ path.lineTo(20, 20);
+ canvas->drawPath(path, paint);
+}
+
+// Test thin lines between two filled regions. The outer edges overlap, but
+// there are no inverted edges to fix.
+void draw_barbell(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
+ SkScalar offset = width * 0.5f;
+ SkPath path;
+ path.moveTo(30, 5);
+ path.lineTo(40 - offset, 15 - offset);
+ path.lineTo(60 + offset, 15 - offset);
+ path.lineTo(70, 5);
+ path.lineTo(70, 25);
+ path.lineTo(60 + offset, 15 + offset);
+ path.lineTo(40 - offset, 15 + offset);
+ path.lineTo(30, 25);
+ canvas->drawPath(path, paint);
+}
+
+// Test a thin rectangle and triangle. The top and bottom inner edges of the
+// rectangle and all inner edges of the triangle invert on stroking.
+void draw_thin_rect_and_triangle(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
+ SkPath path;
+ path.moveTo(30, 5);
+ path.lineTo(30 + width, 5);
+ path.lineTo(30 + width, 25);
+ path.lineTo(30, 25);
+ path.moveTo(40, 5);
+ path.lineTo(40 + width, 5);
+ path.lineTo(40, 25);
+ canvas->drawPath(path, paint);
+}
+
+// Two triangles joined by a very thin bridge. The tiny triangle formed
+// by the inner edges at the bridge is inverted.
+// (These are actually now more phat pants than hipster pants.)
+void draw_hipster_pants(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
+ SkPath path;
+ path.moveTo(10, 10);
+ path.lineTo(10, 20);
+ path.lineTo(50, 10 + width);
+ path.lineTo(90, 20);
+ path.lineTo(90, 10);
+ canvas->drawPath(path, paint);
+}
+
+// A thin z-shape whose interior inverts on stroking. The top and bottom inner edges invert, and
+// the connector edges at the "elbows" intersect the inner edges.
+void draw_skinny_snake(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
+ SkPath path;
+ path.moveTo(20 + width, 10);
+ path.lineTo(20 + width, 20);
+ path.lineTo(10 + width, 30);
+ path.lineTo(10 + width, 40);
+ path.lineTo(10 - width, 40);
+ path.lineTo(10 - width, 30);
+ path.lineTo(20 - width, 20);
+ path.lineTo(20 - width, 10);
+ canvas->drawPath(path, paint);
+}
+
+// Test pointy features whose outer edges extend far to the right on stroking.
+void draw_pointy_golf_club(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
+ SkPath path;
+ path.moveTo(20, 10);
+ path.lineTo(80, 10 + width * 0.5);
+ path.lineTo(30, 10 + width);
+ path.lineTo(30, 20);
+ path.lineTo(20, 20);
+ canvas->drawPath(path, paint);
+}
+
};
-DEF_SIMPLE_GM(thinconcavepaths, canvas, 400, 400) {
+DEF_SIMPLE_GM(thinconcavepaths, canvas, 550, 400) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kFill_Style);
canvas->save();
- for (SkScalar width = 0.5; width < 2.05; width += 0.25) {
+ for (SkScalar width = 0.5f; width < 2.05f; width += 0.25f) {
draw_thin_stroked_rect(canvas, paint, width);
canvas->translate(0, 25);
}
canvas->restore();
canvas->translate(50, 0);
canvas->save();
- for (SkScalar width = 0.5; width < 2.05; width += 0.25) {
+ for (SkScalar width = 0.5f; width < 2.05f; width += 0.25f) {
draw_thin_right_angle(canvas, paint, width);
canvas->translate(0, 25);
}
canvas->restore();
+ canvas->translate(40, 0);
+ canvas->save();
+ for (SkScalar width = 0.2f; width < 2.1f; width += 0.2f) {
+ draw_golf_club(canvas, paint, width);
+ canvas->translate(0, 30);
+ }
+ canvas->restore();
+ canvas->translate(70, 0);
+ canvas->save();
+ for (SkScalar width = 0.2f; width < 2.1f; width += 0.2f) {
+ draw_thin_rect_and_triangle(canvas, paint, width);
+ canvas->translate(0, 30);
+ }
+ canvas->restore();
+ canvas->translate(30, 0);
+ canvas->save();
+
+ for (SkScalar width = 0.2f; width < 2.1f; width += 0.2f) {
+ draw_barbell(canvas, paint, width);
+ canvas->translate(0, 30);
+ }
+ canvas->restore();
+ canvas->translate(80, 0);
+ canvas->save();
+ for (SkScalar width = 0.2f; width < 2.1f; width += 0.2f) {
+ draw_hipster_pants(canvas, paint, width);
+ canvas->translate(0, 30);
+ }
+ canvas->restore();
+ canvas->translate(100, 0);
+ canvas->save();
+ for (SkScalar width = 0.2f; width < 2.1f; width += 0.2f) {
+ draw_skinny_snake(canvas, paint, width);
+ canvas->translate(0, 30);
+ }
+ canvas->restore();
+ canvas->translate(30, 0);
+ canvas->save();
+ for (SkScalar width = 0.2f; width < 2.1f; width += 0.2f) {
+ draw_pointy_golf_club(canvas, paint, width);
+ canvas->translate(0, 30);
+ }
+ canvas->restore();
canvas->translate(100, 0);
}
}
void* emit_triangle(Vertex* v0, Vertex* v1, Vertex* v2, const AAParams* aaParams, void* data) {
+ LOG("emit_triangle (%g, %g) %d\n", v0->fPoint.fX, v0->fPoint.fY, v0->fAlpha);
+ LOG(" (%g, %g) %d\n", v1->fPoint.fX, v1->fPoint.fY, v1->fAlpha);
+ LOG(" (%g, %g) %d\n", v2->fPoint.fX, v2->fPoint.fY, v2->fAlpha);
#if TESSELLATOR_WIREFRAME
data = emit_vertex(v0, aaParams, data);
data = emit_vertex(v1, aaParams, data);
double scale = 1.0f / denom;
point->fX = SkDoubleToScalar((fB * other.fC - other.fB * fC) * scale);
point->fY = SkDoubleToScalar((other.fA * fC - fA * other.fC) * scale);
- round(point);
return true;
}
double fA, fB, fC;
p->fX = SkDoubleToScalar(fTop->fPoint.fX - s * fLine.fB);
p->fY = SkDoubleToScalar(fTop->fPoint.fY + s * fLine.fA);
if (alpha) {
- if (fType == Type::kInner || other.fType == Type::kInner) {
- *alpha = 255;
+ if (fType == Type::kConnector) {
+ *alpha = (1.0 - s) * fTop->fAlpha + s * fBottom->fAlpha;
+ } else if (other.fType == Type::kConnector) {
+ double t = tNumer / denom;
+ *alpha = (1.0 - t) * other.fTop->fAlpha + t * other.fBottom->fAlpha;
} else if (fType == Type::kOuter && other.fType == Type::kOuter) {
*alpha = 0;
} else {
- *alpha = (1.0 - s) * fTop->fAlpha + s * fBottom->fAlpha;
+ *alpha = 255;
}
}
return true;
split_edge(edge, v, activeEdges, c, alloc);
split_edge(other, v, activeEdges, c, alloc);
}
+ v->fAlpha = SkTMax(v->fAlpha, alpha);
return v;
}
return nullptr;
}
}
+void fix_inversions(Vertex* prev, Vertex* next, Edge* prevBisector, Edge* nextBisector,
+ Edge* prevEdge, Comparator& c) {
+ if (!prev || !next) {
+ return;
+ }
+ int winding = c.sweep_lt(prev->fPoint, next->fPoint) ? 1 : -1;
+ SkPoint p;
+ uint8_t alpha;
+ if (winding != prevEdge->fWinding && prevBisector->intersect(*nextBisector, &p, &alpha)) {
+ prev->fPoint = next->fPoint = p;
+ prev->fAlpha = next->fAlpha = alpha;
+ }
+}
+
// Stage 5d: Displace edges by half a pixel inward and outward along their normals. Intersect to
// find new vertices, and set zero alpha on the exterior and one alpha on the interior. Build a
// new antialiased mesh from those vertices.
prevOuter.fC += offset;
VertexList innerVertices;
VertexList outerVertices;
- SkVector prevNormal;
- get_edge_normal(prevEdge, &prevNormal);
+ Edge* prevBisector = nullptr;
for (Edge* e = boundary->fHead; e != nullptr; e = e->fRight) {
double offset = radius * sqrt(e->fLine.magSq()) * e->fWinding;
Line inner(e->fTop, e->fBottom);
get_edge_normal(e, &normal);
if (prevInner.intersect(inner, &innerPoint) &&
prevOuter.intersect(outer, &outerPoint)) {
- // cos(theta) < -0.999 implies a miter angle of ~2.5 degrees,
- // below which we'll bevel the outer edges.
- if (prevNormal.dot(normal) < -0.999) {
- SkPoint p = e->fWinding > 0 ? e->fTop->fPoint : e->fBottom->fPoint;
- SkPoint outerPoint1 = p - prevNormal * radius;
- SkPoint outerPoint2 = p - normal * radius;
- innerVertices.append(ALLOC_NEW(Vertex, (innerPoint, 255), alloc));
- innerVertices.append(ALLOC_NEW(Vertex, (innerPoint, 255), alloc));
- outerVertices.append(ALLOC_NEW(Vertex, (outerPoint1, 0), alloc));
- outerVertices.append(ALLOC_NEW(Vertex, (outerPoint2, 0), alloc));
- } else {
- innerVertices.append(ALLOC_NEW(Vertex, (innerPoint, 255), alloc));
- outerVertices.append(ALLOC_NEW(Vertex, (outerPoint, 0), alloc));
- }
+ Vertex* innerVertex = ALLOC_NEW(Vertex, (innerPoint, 255), alloc);
+ Vertex* outerVertex = ALLOC_NEW(Vertex, (outerPoint, 0), alloc);
+ Edge* bisector = new_edge(outerVertex, innerVertex, Edge::Type::kConnector, c, alloc);
+ fix_inversions(innerVertices.fTail, innerVertex, prevBisector, bisector, prevEdge, c);
+ fix_inversions(outerVertices.fTail, outerVertex, prevBisector, bisector, prevEdge, c);
+ innerVertices.append(innerVertex);
+ outerVertices.append(outerVertex);
+ prevBisector = bisector;
}
prevInner = inner;
prevOuter = outer;
prevEdge = e;
- prevNormal = normal;
}
innerVertices.close();
outerVertices.close();
if (!innerVertex || !outerVertex) {
return;
}
+ Edge* bisector = new_edge(outerVertices.fHead, innerVertices.fHead, Edge::Type::kConnector, c,
+ alloc);
+ fix_inversions(innerVertices.fTail, innerVertices.fHead, prevBisector, bisector, prevEdge, c);
+ fix_inversions(outerVertices.fTail, outerVertices.fHead, prevBisector, bisector, prevEdge, c);
do {
// Connect vertices into a quad mesh. Outer edges get default (1) winding.
// Inner edges get -2 winding. This ensures that the interior is always filled