skia2/gm/complexclip.cpp
Michael Ludwig 4ce77863d5 Fix clip shader coverage combining with geometry processor coverage
We were getting lucky with intersect in that the tested shaders always
ended up multiplying by the input alpha at the end. This adds a
dedicated GM that tests the difference op for clip shaders, across
a variety of shape types.

Bug: skia:10879
Change-Id: I45fdaad27f05ae7a74dbdc79eece2e9688806568
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/330123
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
2020-10-28 15:55:01 +00:00

504 lines
18 KiB
C++

/*
* Copyright 2011 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/SkClipOp.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTypeface.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradientShader.h"
#include "src/core/SkClipOpPriv.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include <string.h>
namespace skiagm {
constexpr SkColor gPathColor = SK_ColorBLACK;
constexpr SkColor gClipAColor = SK_ColorBLUE;
constexpr SkColor gClipBColor = SK_ColorRED;
class ComplexClipGM : public GM {
public:
ComplexClipGM(bool aaclip, bool saveLayer, bool invertDraw)
: fDoAAClip(aaclip)
, fDoSaveLayer(saveLayer)
, fInvertDraw(invertDraw) {
this->setBGColor(0xFFDEDFDE);
}
protected:
SkString onShortName() override {
SkString str;
str.printf("complexclip_%s%s%s",
fDoAAClip ? "aa" : "bw",
fDoSaveLayer ? "_layer" : "",
fInvertDraw ? "_invert" : "");
return str;
}
SkISize onISize() override { return SkISize::Make(388, 780); }
void onDraw(SkCanvas* canvas) override {
SkPath path = SkPathBuilder()
.moveTo(0, 50)
.quadTo(0, 0, 50, 0)
.lineTo(175, 0)
.quadTo(200, 0, 200, 25)
.lineTo(200, 150)
.quadTo(200, 200, 150, 200)
.lineTo(0, 200)
.close()
.moveTo(50, 50)
.lineTo(150, 50)
.lineTo(150, 125)
.quadTo(150, 150, 125, 150)
.lineTo(50, 150)
.close()
.detach();
if (fInvertDraw) {
path.setFillType(SkPathFillType::kInverseEvenOdd);
} else {
path.setFillType(SkPathFillType::kEvenOdd);
}
SkPaint pathPaint;
pathPaint.setAntiAlias(true);
pathPaint.setColor(gPathColor);
SkPath clipA = SkPath::Polygon({{10, 20}, {165, 22}, {70, 105}, {165, 177}, {-5, 180}}, true);
SkPath clipB = SkPath::Polygon({{40, 10}, {190, 15}, {195, 190}, {40, 185}, {155, 100}}, true);
SkFont font(ToolUtils::create_portable_typeface(), 20);
constexpr struct {
SkClipOp fOp;
const char* fName;
} gOps[] = { //extra spaces in names for measureText
{kIntersect_SkClipOp, "Isect "},
{kDifference_SkClipOp, "Diff " },
};
canvas->translate(20, 20);
canvas->scale(3 * SK_Scalar1 / 4, 3 * SK_Scalar1 / 4);
if (fDoSaveLayer) {
// We want the layer to appear symmetric relative to actual
// device boundaries so we need to "undo" the effect of the
// scale and translate
SkRect bounds = SkRect::MakeLTRB(
4.0f/3.0f * -20,
4.0f/3.0f * -20,
4.0f/3.0f * (this->getISize().fWidth - 20),
4.0f/3.0f * (this->getISize().fHeight - 20));
bounds.inset(100, 100);
SkPaint boundPaint;
boundPaint.setColor(SK_ColorRED);
boundPaint.setStyle(SkPaint::kStroke_Style);
canvas->drawRect(bounds, boundPaint);
canvas->clipRect(bounds);
canvas->saveLayer(&bounds, nullptr);
}
for (int invBits = 0; invBits < 4; ++invBits) {
canvas->save();
for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) {
this->drawHairlines(canvas, path, clipA, clipB);
bool doInvA = SkToBool(invBits & 1);
bool doInvB = SkToBool(invBits & 2);
canvas->save();
// set clip
clipA.setFillType(doInvA ? SkPathFillType::kInverseEvenOdd :
SkPathFillType::kEvenOdd);
clipB.setFillType(doInvB ? SkPathFillType::kInverseEvenOdd :
SkPathFillType::kEvenOdd);
canvas->clipPath(clipA, fDoAAClip);
canvas->clipPath(clipB, gOps[op].fOp, fDoAAClip);
// In the inverse case we need to prevent the draw from covering the whole
// canvas.
if (fInvertDraw) {
SkRect rectClip = clipA.getBounds();
rectClip.join(path.getBounds());
rectClip.join(path.getBounds());
rectClip.outset(5, 5);
canvas->clipRect(rectClip);
}
// draw path clipped
canvas->drawPath(path, pathPaint);
canvas->restore();
SkPaint paint;
SkScalar txtX = 45;
paint.setColor(gClipAColor);
const char* aTxt = doInvA ? "InvA " : "A ";
canvas->drawSimpleText(aTxt, strlen(aTxt), SkTextEncoding::kUTF8, txtX, 220, font, paint);
txtX += font.measureText(aTxt, strlen(aTxt), SkTextEncoding::kUTF8);
paint.setColor(SK_ColorBLACK);
canvas->drawSimpleText(gOps[op].fName, strlen(gOps[op].fName), SkTextEncoding::kUTF8, txtX, 220,
font, paint);
txtX += font.measureText(gOps[op].fName, strlen(gOps[op].fName), SkTextEncoding::kUTF8);
paint.setColor(gClipBColor);
const char* bTxt = doInvB ? "InvB " : "B ";
canvas->drawSimpleText(bTxt, strlen(bTxt), SkTextEncoding::kUTF8, txtX, 220, font, paint);
canvas->translate(250,0);
}
canvas->restore();
canvas->translate(0, 250);
}
if (fDoSaveLayer) {
canvas->restore();
}
}
private:
void drawHairlines(SkCanvas* canvas, const SkPath& path,
const SkPath& clipA, const SkPath& clipB) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
const SkAlpha fade = 0x33;
// draw path in hairline
paint.setColor(gPathColor); paint.setAlpha(fade);
canvas->drawPath(path, paint);
// draw clips in hair line
paint.setColor(gClipAColor); paint.setAlpha(fade);
canvas->drawPath(clipA, paint);
paint.setColor(gClipBColor); paint.setAlpha(fade);
canvas->drawPath(clipB, paint);
}
bool fDoAAClip;
bool fDoSaveLayer;
bool fInvertDraw;
using INHERITED = GM;
};
//////////////////////////////////////////////////////////////////////////////
DEF_GM(return new ComplexClipGM(false, false, false);)
DEF_GM(return new ComplexClipGM(false, false, true);)
DEF_GM(return new ComplexClipGM(false, true, false);)
DEF_GM(return new ComplexClipGM(false, true, true);)
DEF_GM(return new ComplexClipGM(true, false, false);)
DEF_GM(return new ComplexClipGM(true, false, true);)
DEF_GM(return new ComplexClipGM(true, true, false);)
DEF_GM(return new ComplexClipGM(true, true, true);)
} // namespace skiagm
DEF_SIMPLE_GM(clip_shader, canvas, 840, 650) {
auto img = GetResourceAsImage("images/yellow_rose.png");
auto sh = img->makeShader();
SkRect r = SkRect::MakeIWH(img->width(), img->height());
SkPaint p;
canvas->translate(10, 10);
canvas->drawImage(img, 0, 0, nullptr);
canvas->save();
canvas->translate(img->width() + 10, 0);
canvas->clipShader(sh, SkClipOp::kIntersect);
p.setColor(SK_ColorRED);
canvas->drawRect(r, p);
canvas->restore();
canvas->save();
canvas->translate(0, img->height() + 10);
canvas->clipShader(sh, SkClipOp::kDifference);
p.setColor(SK_ColorGREEN);
canvas->drawRect(r, p);
canvas->restore();
canvas->save();
canvas->translate(img->width() + 10, img->height() + 10);
canvas->clipShader(sh, SkClipOp::kIntersect);
canvas->save();
SkMatrix lm = SkMatrix::Scale(1.0f/5, 1.0f/5);
canvas->clipShader(img->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &lm));
canvas->drawImage(img, 0, 0, nullptr);
canvas->restore();
canvas->restore();
}
DEF_SIMPLE_GM(clip_shader_layer, canvas, 430, 320) {
auto img = GetResourceAsImage("images/yellow_rose.png");
auto sh = img->makeShader();
SkRect r = SkRect::MakeIWH(img->width(), img->height());
canvas->translate(10, 10);
// now add the cool clip
canvas->clipRect(r);
canvas->clipShader(sh);
// now draw a layer with the same image, and watch it get restored w/ the clip
canvas->saveLayer(&r, nullptr);
canvas->drawColor(0xFFFF0000);
canvas->restore();
}
DEF_SIMPLE_GM(clip_shader_nested, canvas, 256, 256) {
float w = 64.f;
float h = 64.f;
const SkColor gradColors[] = {SK_ColorBLACK, SkColorSetARGB(128, 128, 128, 128)};
auto s = SkGradientShader::MakeRadial({0.5f * w, 0.5f * h}, 0.1f * w, gradColors, nullptr,
2, SkTileMode::kRepeat, 0, nullptr);
SkPaint p;
// A large black rect affected by two gradient clips
canvas->save();
canvas->clipShader(s);
canvas->scale(2.f, 2.f);
canvas->clipShader(s);
canvas->drawRect(SkRect::MakeWH(w, h), p);
canvas->restore();
canvas->translate(0.f, 2.f * h);
// A small red rect, with no clipping
canvas->save();
p.setColor(SK_ColorRED);
canvas->drawRect(SkRect::MakeWH(w, h), p);
canvas->restore();
}
namespace {
// Where is canvas->concat(persp) called relative to the clipShader calls.
enum ConcatPerspective {
kConcatBeforeClips,
kConcatAfterClips,
kConcatBetweenClips
};
// Order in which clipShader(image) and clipShader(gradient) are specified; only meaningful
// when CanvasPerspective is kConcatBetweenClips.
enum ClipOrder {
kClipImageFirst,
kClipGradientFirst,
kDoesntMatter = kClipImageFirst
};
// Which shaders have perspective applied as a local matrix.
enum LocalMatrix {
kNoLocalMat,
kImageWithLocalMat,
kGradientWithLocalMat,
kBothWithLocalMat
};
struct Config {
ConcatPerspective fConcat;
ClipOrder fOrder;
LocalMatrix fLM;
};
static void draw_banner(SkCanvas* canvas, Config config) {
SkString banner;
banner.append("Persp: ");
if (config.fConcat == kConcatBeforeClips || config.fLM == kBothWithLocalMat) {
banner.append("Both Clips");
} else {
SkASSERT((config.fConcat == kConcatBetweenClips && config.fLM == kNoLocalMat) ||
(config.fConcat == kConcatAfterClips && (config.fLM == kImageWithLocalMat ||
config.fLM == kGradientWithLocalMat)));
if ((config.fConcat == kConcatBetweenClips && config.fOrder == kClipImageFirst) ||
config.fLM == kGradientWithLocalMat) {
banner.append("Gradient");
} else {
SkASSERT(config.fOrder == kClipGradientFirst || config.fLM == kImageWithLocalMat);
banner.append("Image");
}
}
if (config.fLM != kNoLocalMat) {
banner.append(" (w/ LM, should equal top row)");
}
static const SkFont kFont(ToolUtils::create_portable_typeface(), 12);
canvas->drawString(banner.c_str(), 20.f, -30.f, kFont, SkPaint());
};
} // namespace
DEF_SIMPLE_GM(clip_shader_persp, canvas, 1370, 1030) {
// Each draw has a clipShader(image-shader), a clipShader(gradient-shader), a concat(persp-mat),
// and each shader may or may not be wrapped with a perspective local matrix.
// Pairs of configs that should match in appearance where first config doesn't use a local
// matrix (top row of GM) and the second does (bottom row of GM).
Config matches[][2] = {
// Everything has perspective
{{kConcatBeforeClips, kDoesntMatter, kNoLocalMat},
{kConcatAfterClips, kDoesntMatter, kBothWithLocalMat}},
// Image shader has perspective
{{kConcatBetweenClips, kClipGradientFirst, kNoLocalMat},
{kConcatAfterClips, kDoesntMatter, kImageWithLocalMat}},
// Gradient shader has perspective
{{kConcatBetweenClips, kClipImageFirst, kNoLocalMat},
{kConcatAfterClips, kDoesntMatter, kGradientWithLocalMat}}
};
// The image that is drawn
auto img = GetResourceAsImage("images/yellow_rose.png");
// Scale factor always applied to the image shader so that it tiles
SkMatrix scale = SkMatrix::Scale(1.f / 4.f, 1.f / 4.f);
// The perspective matrix applied wherever needed
SkPoint src[4];
SkRect::Make(img->dimensions()).toQuad(src);
SkPoint dst[4] = {{0, 80.f},
{img->width() + 28.f, -100.f},
{img->width() - 28.f, img->height() + 100.f},
{0.f, img->height() - 80.f}};
SkMatrix persp;
SkAssertResult(persp.setPolyToPoly(src, dst, 4));
SkMatrix perspScale = SkMatrix::Concat(persp, scale);
auto drawConfig = [&](Config config) {
canvas->save();
draw_banner(canvas, config);
// Make clipShaders (possibly with local matrices)
bool gradLM = config.fLM == kGradientWithLocalMat || config.fLM == kBothWithLocalMat;
const SkColor gradColors[] = {SK_ColorBLACK, SkColorSetARGB(128, 128, 128, 128)};
auto gradShader = SkGradientShader::MakeRadial({0.5f * img->width(), 0.5f * img->height()},
0.1f * img->width(), gradColors, nullptr, 2,
SkTileMode::kRepeat, 0,
gradLM ? &persp : nullptr);
bool imageLM = config.fLM == kImageWithLocalMat || config.fLM == kBothWithLocalMat;
auto imgShader = img->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
imageLM ? perspScale : scale);
// Perspective before any clipShader
if (config.fConcat == kConcatBeforeClips) {
canvas->concat(persp);
}
// First clipshader
canvas->clipShader(config.fOrder == kClipImageFirst ? imgShader : gradShader);
// Perspective between clipShader
if (config.fConcat == kConcatBetweenClips) {
canvas->concat(persp);
}
// Second clipShader
canvas->clipShader(config.fOrder == kClipImageFirst ? gradShader : imgShader);
// Perspective after clipShader
if (config.fConcat == kConcatAfterClips) {
canvas->concat(persp);
}
// Actual draw and clip boundary are the same for all configs
canvas->clipRect(SkRect::MakeIWH(img->width(), img->height()));
canvas->clear(SK_ColorBLACK);
canvas->drawImage(img, 0, 0);
canvas->restore();
};
SkIRect grid = persp.mapRect(SkRect::Make(img->dimensions())).roundOut();
grid.fLeft -= 20; // manual adjust to look nicer
canvas->translate(10.f, 10.f);
for (size_t i = 0; i < SK_ARRAY_COUNT(matches); ++i) {
canvas->save();
canvas->translate(-grid.fLeft, -grid.fTop);
drawConfig(matches[i][0]);
canvas->translate(0.f, grid.height());
drawConfig(matches[i][1]);
canvas->restore();
canvas->translate(grid.width(), 0.f);
}
}
DEF_SIMPLE_GM(clip_shader_difference, canvas, 512, 512) {
auto image = GetResourceAsImage("images/yellow_rose.png");
canvas->clear(SK_ColorGRAY);
SkRect rect = SkRect::MakeWH(256, 256);
SkMatrix local = SkMatrix::MakeRectToRect(SkRect::MakeWH(image->width(), image->height()),
SkRect::MakeWH(64, 64), SkMatrix::kFill_ScaleToFit);
auto shader = image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &local);
SkPaint paint;
paint.setColor(SK_ColorRED);
paint.setAntiAlias(true);
// TL: A rectangle
{
canvas->save();
canvas->translate(0, 0);
canvas->clipShader(shader, SkClipOp::kDifference);
canvas->drawRect(rect, paint);
canvas->restore();
}
// TR: A round rectangle
{
canvas->save();
canvas->translate(256, 0);
canvas->clipShader(shader, SkClipOp::kDifference);
canvas->drawRRect(SkRRect::MakeRectXY(rect, 64.f, 64.f), paint);
canvas->restore();
}
// BL: A path
{
canvas->save();
canvas->translate(0, 256);
canvas->clipShader(shader, SkClipOp::kDifference);
SkPath path;
path.moveTo(0.f, 128.f);
path.lineTo(128.f, 256.f);
path.lineTo(256.f, 128.f);
path.lineTo(128.f, 0.f);
SkScalar d = 64.f * SK_ScalarSqrt2;
path.moveTo(128.f - d, 128.f - d);
path.lineTo(128.f - d, 128.f + d);
path.lineTo(128.f + d, 128.f + d);
path.lineTo(128.f + d, 128.f - d);
canvas->drawPath(path, paint);
canvas->restore();
}
// BR: Text
{
canvas->save();
canvas->translate(256, 256);
canvas->clipShader(shader, SkClipOp::kDifference);
for (int y = 0; y < 4; ++y) {
canvas->drawString("Hello", 32.f, y * 64.f, SkFont(nullptr, 64.f), paint);
}
canvas->restore();
}
}