skia2/gm/path_stroke_with_zero_length.cpp
Adlai Holler bcfc554fde Add GrDirectContext arg to SkImage::readPixels
Note: The polarity of the staging flag is inverted from usual because
a G3 dependency with no SkUserConfig.h relies on the legacy API.

Once this lands, we will migrate them and others, then remove the
staging API. The inverted staging flag is kind of nice, actually - I may
use that pattern in the future. It means less total CLs and it's just as
easy to flip the bit on or off during debugging.

Bug: skia:104662
Change-Id: I48cba1eeae3e2e6f79918c6d243e0666e68ec71b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/310656
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Adlai Holler <adlai@google.com>
2020-08-27 19:26:29 +00:00

269 lines
9.0 KiB
C++

/*
* Copyright 2015 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/SkColor.h"
#include "include/core/SkColorPriv.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTypes.h"
#include "include/gpu/GrDirectContext.h"
#include "include/utils/SkParsePath.h"
#include "src/core/SkAutoPixmapStorage.h"
// GM to test combinations of stroking zero length paths with different caps and other settings
// Variables:
// * Antialiasing: On, Off
// * Caps: Butt, Round, Square
// * Stroke width: 0, 0.9, 1, 1.1, 15, 25
// * Path form: M, ML, MLZ, MZ
// * Path contours: 1 or 2
// * Path verbs: Line, Quad, Cubic, Conic
//
// Each test is drawn to a 50x20 offscreen surface, and expected to produce some number (0 - 2) of
// visible pieces of cap geometry. These are counted by scanning horizontally for peaks (blobs).
static bool draw_path_cell(GrDirectContext* dContext, SkCanvas* canvas, SkImage* img,
int expectedCaps) {
// Draw the image
canvas->drawImage(img, 0, 0);
int w = img->width(), h = img->height();
// Read the pixels back
SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
SkAutoPixmapStorage pmap;
pmap.alloc(info);
if (!img->readPixels(dContext, pmap, 0, 0)) {
return false;
}
// To account for rasterization differences, we scan the middle two rows [y, y+1] of the image
SkASSERT(h % 2 == 0);
int y = (h - 1) / 2;
bool inBlob = false;
int numBlobs = 0;
for (int x = 0; x < w; ++x) {
// We drew white-on-black. We can look for any non-zero value. Just check red.
// And we care if either row is non-zero, so just add them to simplify everything.
uint32_t v = SkGetPackedR32(*pmap.addr32(x, y)) + SkGetPackedR32(*pmap.addr32(x, y + 1));
if (!inBlob && v) {
++numBlobs;
}
inBlob = SkToBool(v);
}
SkPaint outline;
outline.setStyle(SkPaint::kStroke_Style);
if (numBlobs == expectedCaps) {
outline.setColor(0xFF007F00); // Green
} else if (numBlobs > expectedCaps) {
outline.setColor(0xFF7F7F00); // Yellow -- more geometry than expected
} else {
outline.setColor(0xFF7F0000); // Red -- missing some geometry
}
canvas->drawRect(SkRect::MakeWH(w, h), outline);
return numBlobs == expectedCaps;
}
static const SkPaint::Cap kCaps[] = {
SkPaint::kButt_Cap,
SkPaint::kRound_Cap,
SkPaint::kSquare_Cap
};
static const SkScalar kWidths[] = { 0.0f, 0.9f, 1.0f, 1.1f, 15.0f, 25.0f };
// Full set of path structures for single contour case (each primitive with and without a close)
static const char* kAllVerbs[] = {
nullptr,
"z ",
"l 0 0 ",
"l 0 0 z ",
"q 0 0 0 0 ",
"q 0 0 0 0 z ",
"c 0 0 0 0 0 0 ",
"c 0 0 0 0 0 0 z ",
"a 0 0 0 0 0 0 0 ",
"a 0 0 0 0 0 0 0 z "
};
// Reduced set of path structures for double contour case, to keep total number of cases down
static const char* kSomeVerbs[] = {
nullptr,
"z ",
"l 0 0 ",
"l 0 0 z ",
"q 0 0 0 0 ",
"q 0 0 0 0 z ",
};
static const int kCellWidth = 50;
static const int kCellHeight = 20;
static const int kCellPad = 2;
static const int kNumRows = SK_ARRAY_COUNT(kCaps) * SK_ARRAY_COUNT(kWidths);
static const int kNumColumns = SK_ARRAY_COUNT(kAllVerbs);
static const int kTotalWidth = kNumColumns * (kCellWidth + kCellPad) + kCellPad;
static const int kTotalHeight = kNumRows * (kCellHeight + kCellPad) + kCellPad;
static const int kDblContourNumColums = SK_ARRAY_COUNT(kSomeVerbs) * SK_ARRAY_COUNT(kSomeVerbs);
static const int kDblContourTotalWidth = kDblContourNumColums * (kCellWidth + kCellPad) + kCellPad;
// 50% transparent versions of the colors used for positive/negative triage icons on gold.skia.org
static const SkColor kFailureRed = 0x7FE7298A;
static const SkColor kSuccessGreen = 0x7F1B9E77;
static void draw_zero_length_capped_paths(SkCanvas* canvas, bool aa) {
auto rContext = canvas->recordingContext();
auto dContext = GrAsDirectContext(rContext);
SkASSERT(dContext || !rContext); // not supported in DDL.
canvas->translate(kCellPad, kCellPad);
SkImageInfo info = canvas->imageInfo().makeWH(kCellWidth, kCellHeight);
auto surface = canvas->makeSurface(info);
if (!surface) {
surface = SkSurface::MakeRasterN32Premul(kCellWidth, kCellHeight);
}
SkPaint paint;
paint.setColor(SK_ColorWHITE);
paint.setAntiAlias(aa);
paint.setStyle(SkPaint::kStroke_Style);
int numFailedTests = 0;
for (auto cap : kCaps) {
for (auto width : kWidths) {
paint.setStrokeCap(cap);
paint.setStrokeWidth(width);
canvas->save();
for (auto verb : kAllVerbs) {
SkString pathStr;
pathStr.appendf("M %f %f ", (kCellWidth - 1) * 0.5f, (kCellHeight - 1) * 0.5f);
if (verb) {
pathStr.append(verb);
}
SkPath path;
SkParsePath::FromSVGString(pathStr.c_str(), &path);
surface->getCanvas()->clear(SK_ColorTRANSPARENT);
surface->getCanvas()->drawPath(path, paint);
auto img = surface->makeImageSnapshot();
// All cases should draw one cap, except for butt capped, and dangling moves
// (without a verb or close), which shouldn't draw anything.
int expectedCaps = ((SkPaint::kButt_Cap == cap) || !verb) ? 0 : 1;
if (!draw_path_cell(dContext, canvas, img.get(), expectedCaps)) {
++numFailedTests;
}
canvas->translate(kCellWidth + kCellPad, 0);
}
canvas->restore();
canvas->translate(0, kCellHeight + kCellPad);
}
}
canvas->drawColor(numFailedTests > 0 ? kFailureRed : kSuccessGreen);
}
DEF_SIMPLE_GM_BG(zero_length_paths_aa, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
draw_zero_length_capped_paths(canvas, true);
}
DEF_SIMPLE_GM_BG(zero_length_paths_bw, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
draw_zero_length_capped_paths(canvas, false);
}
static void draw_zero_length_capped_paths_dbl_contour(SkCanvas* canvas, bool aa) {
auto rContext = canvas->recordingContext();
auto dContext = GrAsDirectContext(rContext);
SkASSERT(dContext || !rContext); // not supported in DDL.
canvas->translate(kCellPad, kCellPad);
SkImageInfo info = canvas->imageInfo().makeWH(kCellWidth, kCellHeight);
auto surface = canvas->makeSurface(info);
if (!surface) {
surface = SkSurface::MakeRasterN32Premul(kCellWidth, kCellHeight);
}
SkPaint paint;
paint.setColor(SK_ColorWHITE);
paint.setAntiAlias(aa);
paint.setStyle(SkPaint::kStroke_Style);
int numFailedTests = 0;
for (auto cap : kCaps) {
for (auto width : kWidths) {
paint.setStrokeCap(cap);
paint.setStrokeWidth(width);
canvas->save();
for (auto firstVerb : kSomeVerbs) {
for (auto secondVerb : kSomeVerbs) {
int expectedCaps = 0;
SkString pathStr;
pathStr.append("M 9.5 9.5 ");
if (firstVerb) {
pathStr.append(firstVerb);
++expectedCaps;
}
pathStr.append("M 40.5 9.5 ");
if (secondVerb) {
pathStr.append(secondVerb);
++expectedCaps;
}
SkPath path;
SkParsePath::FromSVGString(pathStr.c_str(), &path);
surface->getCanvas()->clear(SK_ColorTRANSPARENT);
surface->getCanvas()->drawPath(path, paint);
auto img = surface->makeImageSnapshot();
if (SkPaint::kButt_Cap == cap) {
expectedCaps = 0;
}
if (!draw_path_cell(dContext, canvas, img.get(), expectedCaps)) {
++numFailedTests;
}
canvas->translate(kCellWidth + kCellPad, 0);
}
}
canvas->restore();
canvas->translate(0, kCellHeight + kCellPad);
}
}
canvas->drawColor(numFailedTests > 0 ? kFailureRed : kSuccessGreen);
}
DEF_SIMPLE_GM_BG(zero_length_paths_dbl_aa, canvas, kDblContourTotalWidth, kTotalHeight,
SK_ColorBLACK) {
draw_zero_length_capped_paths_dbl_contour(canvas, true);
}
DEF_SIMPLE_GM_BG(zero_length_paths_dbl_bw, canvas, kDblContourTotalWidth, kTotalHeight,
SK_ColorBLACK) {
draw_zero_length_capped_paths_dbl_contour(canvas, false);
}