This brings hairlines into agreement with thick strokes.

Add more testing and a pixel magnification to GM.

R=reed@google.com, fmalita@chromium.org
BUG=skia:4599
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1527083002

Review URL: https://codereview.chromium.org/1527083002
This commit is contained in:
caryclark 2015-12-16 08:53:41 -08:00 committed by Commit bot
parent da420b976e
commit 1f17ab5992
2 changed files with 178 additions and 33 deletions

View File

@ -9,46 +9,187 @@
#include "SkStream.h"
#include "gm.h"
// Test how short paths are stroked with various caps
DEF_SIMPLE_GM(path_stroke_with_zero_length, canvas, 240, 120) {
SkPath paths[5];
paths[0].moveTo(30.0f, 0); // single line segment
paths[0].rLineTo(30.0f, 0);
class StrokeZeroGM : public skiagm::GM {
SkPath fPaths[8];
SkPath fClipL, fClipR, fClipS;
paths[1].moveTo(90.0f, 0); // single line segment with close
paths[1].rLineTo(30.0f, 0);
paths[1].close();
protected:
void onOnceBeforeDraw() override {
fClipL.moveTo(0, 0);
fClipL.lineTo(3, 0);
fClipL.lineTo(2.5f, 1);
fClipL.lineTo(3.5f, 2.5f);
fClipL.lineTo(2.5f, 4);
fClipL.lineTo(3, 5);
fClipL.lineTo(0, 5);
fClipL.close();
paths[2].moveTo(150.0f, 0); // zero-length line
paths[2].rLineTo(0, 0);
fClipR.moveTo(34, 0);
fClipR.lineTo(34, 5);
fClipR.lineTo(31, 5);
fClipR.lineTo(30.5, 4);
fClipR.lineTo(31.5, 2.5);
fClipR.lineTo(30.5, 1);
fClipR.lineTo(31, 0);
fClipR.close();
paths[3].moveTo(180.0f, 0); // zero-length line with close
paths[3].rLineTo(0, 0);
paths[3].close();
fClipS.addRect(SkRect::MakeIWH(4, 5));
paths[4].moveTo(210.0f, 0); // close only, no line
paths[4].close();
fPaths[0].moveTo(30, 0); // single line segment
fPaths[0].rLineTo(30, 0);
auto drawPaths = [&](const SkPaint& paint) {
canvas->translate(0, 30.0f);
for (const SkPath& path : paths) {
canvas->drawPath(path, paint);
fPaths[1].moveTo(90, 0); // single line segment with close (does not draw caps)
fPaths[1].rLineTo(30, 0);
fPaths[1].close();
fPaths[2].moveTo(150, 0); // zero-length line
fPaths[2].rLineTo(0, 0);
fPaths[3].moveTo(180, 0); // zero-length line with close (expected not to draw)
fPaths[3].rLineTo(0, 0);
fPaths[3].close();
fPaths[4].moveTo(210, 0); // close only, no line
fPaths[4].close();
fPaths[5].moveTo(30, 90); // all combos below should draw two caps
fPaths[5].rLineTo(0, 0);
fPaths[5].moveTo(60, 90);
fPaths[5].rLineTo(0, 0);
fPaths[6].moveTo(90, 90);
fPaths[6].close();
fPaths[6].moveTo(120, 90);
fPaths[6].close();
fPaths[7].moveTo(150, 90);
fPaths[7].rLineTo(0, 0);
fPaths[7].moveTo(180, 90);
fPaths[7].close();
}
SkString onShortName() override {
return SkString("path_stroke_with_zero_length");
}
SkISize onISize() override {
return SkISize::Make(1120, 840);
}
void onDraw(SkCanvas* canvas) override {
SkPaint bkgrnd;
bkgrnd.setColor(SK_ColorWHITE);
canvas->drawRect(SkRect::MakeIWH(onISize().fWidth, onISize().fHeight), bkgrnd);
auto drawPaths = [&](SkPaint& paint, int indexMask) {
canvas->translate(0, 30.0f);
int index = 0;
for (const SkPath& path : fPaths) {
if (indexMask & (1 << index)) {
canvas->drawPath(path, paint);
}
if (paint.getStrokeWidth() < 2) {
drawFat(canvas, path, paint, index);
}
++index;
}
};
if (false) { // debugging variant that draws a single element
SkScalar width = 0;
bool antialias = true;
SkPaint butt;
butt.setAntiAlias(antialias);
butt.setStyle(SkPaint::kStroke_Style);
butt.setStrokeWidth(width);
SkPaint round(butt);
round.setStrokeCap(SkPaint::kRound_Cap);
drawPaths(round, 1 << 7);
return;
}
};
SkAutoCanvasRestore autoCanvasRestore(canvas, true);
SkPaint butt;
butt.setStyle(SkPaint::kStroke_Style);
butt.setStrokeWidth(20.0f);
butt.setStrokeCap(SkPaint::kButt_Cap);
drawPaths(butt);
SkScalar widths[] = { 0, .999f, 1, 1.001f, 20 };
bool aliases[] = { false, true };
for (bool antialias : aliases) {
canvas->save();
for (SkScalar width : widths) {
canvas->save();
SkPaint butt;
butt.setAntiAlias(antialias);
butt.setStyle(SkPaint::kStroke_Style);
butt.setStrokeWidth(width);
drawPaths(butt, -1);
SkPaint round(butt);
round.setStrokeCap(SkPaint::kRound_Cap);
drawPaths(round);
SkPaint round(butt);
round.setStrokeCap(SkPaint::kRound_Cap);
drawPaths(round, -1);
SkPaint square(butt);
square.setStrokeCap(SkPaint::kSquare_Cap);
drawPaths(square, -1);
canvas->restore();
canvas->translate(220, 0);
}
canvas->restore();
canvas->translate(0, 210);
}
}
private:
void drawFat(SkCanvas* canvas, const SkPath& path, const SkPaint& paint, int index) {
const SkScalar scale = 10;
SkRect bounds = path.getBounds();
SkBitmap offscreen;
offscreen.allocN32Pixels(SkScalarRoundToInt(bounds.width() + 4),
SkScalarRoundToInt(bounds.height() + 4));
SkScalar pathX = bounds.fLeft - 2;
SkScalar pathY = bounds.fTop - 2;
SkMatrix cMatrix = canvas->getTotalMatrix();
if (!canvas->readPixels(&offscreen, SkScalarRoundToInt(pathX + cMatrix.getTranslateX()),
SkScalarRoundToInt(pathY + cMatrix.getTranslateY()))) {
return;
}
canvas->save();
SkMatrix clipM;
clipM.reset();
clipM.preScale(scale, scale);
clipM.postTranslate(bounds.fLeft - 17, bounds.fTop - 24.5f + 420);
SkPath clip;
if (index < 2) {
fClipL.transform(clipM, &clip);
} else {
fClipS.transform(clipM, &clip);
}
canvas->clipPath(clip, SkRegion::kIntersect_Op, true);
canvas->scale(scale, scale);
canvas->drawBitmap(offscreen, (bounds.fLeft - 17) / scale,
(bounds.fTop - 20 + 420) / scale);
canvas->restore();
if (bounds.width() > 20) {
canvas->save();
clipM.reset();
clipM.preScale(scale, scale);
clipM.postTranslate(bounds.fLeft - 17 - 275, bounds.fTop - 24.5f + 420);
SkPath clip;
fClipR.transform(clipM, &clip);
canvas->clipPath(clip, SkRegion::kIntersect_Op, true);
canvas->scale(10.f, 10.f);
canvas->drawBitmap(offscreen, (bounds.fLeft - 17 - 275
+ (index >= 5 ? 5 : 0)) / scale, (bounds.fTop - 20 + 420) / scale);
canvas->restore();
}
}
};
///////////////////////////////////////////////////////////////////////////////
DEF_GM( return new StrokeZeroGM(); )
SkPaint square(butt);
square.setStrokeCap(SkPaint::kSquare_Cap);
drawPaths(square);
}

View File

@ -471,6 +471,10 @@ void hair_path(const SkPath& path, const SkRasterClip& rclip, SkBlitter* blitter
case SkPath::kClose_Verb:
pts[0] = lastPt;
pts[1] = firstPt;
if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) {
// cap moveTo/close to match svg expectations for degenerate segments
extend_pts<capStyle>(prevVerb, iter.peek(), pts, 2);
}
lineproc(pts, 2, clip, blitter);
break;
case SkPath::kDone_Verb: