skia2/gm/thinconcavepaths.cpp
Stephen White 92eba8a5ef GrTessellator (AA): improve antialiasing of thin shapes.
(Long description, but actually quite a small change.)

This is the first change inspired by the "straight skeleton" algorithm.
This algorithm gives us a mental model to describe the edge-antialiasing
problem: consider the shape as walls of a house, the outer alpha
geometry is a 45-degree "roof" up to a flat top at a height of 1.0
(the filled interior).  The faces of the sloping roof join at the
bisectors between the inner and outer points.

When the shape being drawn is sufficiently thin, there should be no flat
roof, and the sloping roof meets at an edge (the straight skeleton).

This patch detects cases where an edge inverts on stroking, which
indicates that the flat roof has turned inside out, and should be
reduced to a point instead. The model above describes what to do:
follow down the "roof" along the bisectors to their intersection.
This is the point to which an inverted edge should be collapsed.
Fortunately, the bisector edges are easy to compute: they're the
connector edges joining inner and outer points. Linearly interpolating
the distance from the top to the bottom point gives the alpha we
should use to approximate coverage.

Now that we are correctly handling inversions, bevelling outer edges
is no longer necesary, since pointy outer edges won't cause nasty
opaque artifacts.

A couple of other quality improvements: on intersection, always lerp
the alpha of connector edge, even if the opposite edge is an inner edge
(later, when these edges are collapsed, we need this value to compute
the correct alpha). Fix the case where an intruding outer vertex
intersects exactly with an inner edge by maxing its alpha with the
computed value in check_for_intersection(). Finally, we also no longer
round off the intersections produced by Line::intersect(), since it
introduces a loss of quality with no measurable performance benefit.

Change-Id: I6fd93df3a57fffc0895e8cb68adbdba626ded0f1
Reviewed-on: https://skia-review.googlesource.com/8028
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Stephan White <senorblanco@chromium.org>
2017-02-06 21:40:05 +00:00

185 lines
5.7 KiB
C++

/*
* Copyright 2017 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 "SkCanvas.h"
#include "SkPath.h"
namespace {
// Test thin stroked rect (stroked "by hand", not by stroking).
void draw_thin_stroked_rect(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
SkPath path;
path.moveTo(10 + width, 10 + width);
path.lineTo(40, 10 + width);
path.lineTo(40, 20);
path.lineTo(10 + width, 20);
path.moveTo(10, 10);
path.lineTo(10, 20 + width);
path.lineTo(40 + width, 20 + width);
path.lineTo(40 + width, 10);
canvas->drawPath(path, paint);
}
void draw_thin_right_angle(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
SkPath path;
path.moveTo(10 + width, 10 + width);
path.lineTo(40, 10 + width);
path.lineTo(40, 20);
path.lineTo(40 + width, 20 + width);
path.lineTo(40 + width, 10);
path.lineTo(10, 10);
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, 550, 400) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kFill_Style);
canvas->save();
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.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);
}