skia2/gm/shadermaskfilter.cpp
Michael Ludwig eced98b5ea Enforce saveLayer ignoring restore paint's mask filter
Documentation specifies that only the alpha, color filter, image filter
and blend mode are used when restoring the saved layer. The coverage that
would be sent to the mask filter is ill-defined, whereas the explicit
boundary of the separate clip image gets around this.

This also removes the issue of the order in which the mask filter and
image filter would be applied.

Some GMs exercised the save layer with mask filter, but those have
been updated to either expect the MF to be ignored, or modified to
no longer use saveLayer+MF. While Android and Flutter expose a saveLayer
with general Paint API, their documentation matches SkCanvas so hopefully
no one is actually relying on the mask filter in those cases. It does
not appear as if Chrome ever tries to use a saveLayer+MF.

Bug: skia:9561

Change-Id: I67a350fbf565683ea60ea88f83a91270e7dcd187
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/274508
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
2020-03-04 15:28:01 +00:00

268 lines
10 KiB
C++

/*
* Copyright 2018 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/SkBlendMode.h"
#include "include/core/SkBlurTypes.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkCoverageMode.h"
#include "include/core/SkFont.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradientShader.h"
#include "include/effects/SkImageFilters.h"
#include "include/effects/SkShaderMaskFilter.h"
#include "include/utils/SkTextUtils.h"
#include "src/core/SkBlendModePriv.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include <initializer_list>
static void draw_masked_image(SkCanvas* canvas, const SkImage* image, SkScalar x, SkScalar y,
const SkImage* mask, sk_sp<SkMaskFilter> outer, SkBlendMode mode) {
SkMatrix matrix = SkMatrix::MakeScale(SkIntToScalar(image->width()) / mask->width(),
SkIntToScalar(image->height() / mask->height()));
// The geometry of the drawImage is also translated by (x,y) so make the mask filter's
// coordinate system align with the rendered rectangle.
matrix.postTranslate(x, y);
SkPaint paint;
auto mf = SkShaderMaskFilter::Make(mask->makeShader(&matrix));
if (outer) {
mf = SkMaskFilter::MakeCompose(outer->makeWithMatrix(matrix), mf);
}
paint.setMaskFilter(mf);
paint.setAntiAlias(true);
paint.setBlendMode(mode);
canvas->drawImage(image, x, y, &paint);
}
static sk_sp<SkShader> make_shader(const SkRect& r) {
const SkPoint pts[] = {
{ r.fLeft, r.fTop }, { r.fRight, r.fBottom },
};
const SkColor colors[] = { 0, SK_ColorWHITE };
return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kRepeat);
}
DEF_SIMPLE_GM(shadermaskfilter_gradient, canvas, 512, 512) {
SkRect r = { 0, 0, 100, 150 };
auto shader = make_shader(r);
auto mf = SkShaderMaskFilter::Make(shader);
canvas->translate(20, 20);
canvas->scale(2, 2);
SkPaint paint;
paint.setMaskFilter(mf);
paint.setColor(SK_ColorRED);
paint.setAntiAlias(true);
canvas->drawOval(r, paint);
}
DEF_SIMPLE_GM_CAN_FAIL(shadermaskfilter_image, canvas, errorMsg, 560, 370) {
canvas->scale(1.25f, 1.25f);
auto image = GetResourceAsImage("images/mandrill_128.png");
auto mask = GetResourceAsImage("images/color_wheel.png");
if (!image || !mask) {
*errorMsg = "Could not load images. Did you forget to set the resourcePath?";
return skiagm::DrawResult::kFail;
}
auto blurmf = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5);
auto gradmf = SkShaderMaskFilter::Make(make_shader(SkRect::MakeIWH(mask->width(),
mask->height())));
const sk_sp<SkMaskFilter> array[] = { nullptr , blurmf, gradmf };
for (SkBlendMode mode : {SkBlendMode::kSrcOver, SkBlendMode::kSrcIn}) {
canvas->save();
for (sk_sp<SkMaskFilter> mf : array) {
draw_masked_image(canvas, image.get(), 10, 10, mask.get(), mf, mode);
canvas->translate(image->width() + 20.f, 0);
}
canvas->restore();
canvas->translate(0, image->height() + 20.f);
}
return skiagm::DrawResult::kOk;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static sk_sp<SkMaskFilter> make_path_mf(const SkPath& path, unsigned alpha) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setAlpha(alpha);
SkPictureRecorder recorder;
recorder.beginRecording(1000, 1000)->drawPath(path, paint);
auto shader = recorder.finishRecordingAsPicture()->makeShader(SkTileMode::kClamp,
SkTileMode::kClamp);
return SkShaderMaskFilter::Make(shader);
}
typedef void (*MakePathsProc)(const SkRect&, SkPath*, SkPath*);
const char* gCoverageName[] = {
"union", "sect", "diff", "rev-diff", "xor"
};
DEF_SIMPLE_GM(combinemaskfilter, canvas, 560, 510) {
const SkRect r = { 0, 0, 100, 100 };
SkPaint paint;
paint.setColor(SK_ColorRED);
SkFont font;
font.setSize(20);
const SkRect r2 = r.makeOutset(1.5f, 1.5f);
SkPaint strokePaint;
strokePaint.setStyle(SkPaint::kStroke_Style);
auto proc0 = [](const SkRect& r, SkPath* pathA, SkPath* pathB) {
pathA->moveTo(r.fLeft, r.fBottom);
pathA->lineTo(r.fRight, r.fTop);
pathA->lineTo(r.fRight, r.fBottom);
pathB->moveTo(r.fLeft, r.fTop);
pathB->lineTo(r.fRight, r.fBottom);
pathB->lineTo(r.fLeft, r.fBottom);
};
auto proc1 = [](const SkRect& r, SkPath* pathA, SkPath* pathB) {
pathA->addCircle(r.width()*0.25f, r.height()*0.25f, r.width()*0.5f);
pathB->addCircle(r.width()*0.75f, r.height()*0.75f, r.width()*0.5f);
};
MakePathsProc procs[] = { proc0, proc1 };
sk_sp<SkMaskFilter> mfA[2], mfB[2];
for (int i = 0; i < 2; ++i) {
SkPath a, b;
procs[i](r, &a, &b);
mfA[i] = make_path_mf(a, 1 * 0xFF / 3);
mfB[i] = make_path_mf(b, 2 * 0xFF / 3);
}
canvas->translate(10, 10 + 20);
canvas->save();
for (int i = 0; i < 5; ++i) {
SkTextUtils::DrawString(canvas, gCoverageName[i], r.width()*0.5f, -10, font, SkPaint(),
SkTextUtils::kCenter_Align);
SkCoverageMode cmode = static_cast<SkCoverageMode>(i);
canvas->save();
// esp. on gpu side, its valuable to exercise modes that do and do-not convolve coverage
// with alpha. SrcOver and SrcIn have these properties, but also happen to "look" the same
// for this test.
const SkBlendMode bmodes[] = { SkBlendMode::kSrcOver, SkBlendMode::kSrcIn };
SkASSERT( SkBlendMode_SupportsCoverageAsAlpha(bmodes[0])); // test as-alpha
SkASSERT(!SkBlendMode_SupportsCoverageAsAlpha(bmodes[1])); // test not-as-alpha
for (auto bmode : bmodes) {
paint.setBlendMode(bmode);
for (int j = 0; j < 2; ++j) {
paint.setMaskFilter(SkMaskFilter::MakeCombine(mfA[j], mfB[j], cmode));
canvas->drawRect(r2, strokePaint);
canvas->drawRect(r, paint);
canvas->translate(0, r.height() + 10);
}
canvas->translate(0, 40);
}
canvas->restore();
canvas->translate(r.width() + 10, 0);
}
canvas->restore();
}
static void draw_mask(SkCanvas* canvas) {
SkPaint p;
p.setAntiAlias(true);
canvas->drawOval(SkRect::Make(canvas->imageInfo().bounds()), p);
}
DEF_SIMPLE_GM(shadermaskfilter_localmatrix, canvas, 1500, 1000) {
static constexpr SkScalar kSize = 100;
using ShaderMakerT = sk_sp<SkShader>(*)(SkCanvas*, const SkMatrix& lm);
static const ShaderMakerT gShaderMakers[] = {
[](SkCanvas* canvas, const SkMatrix& lm) -> sk_sp<SkShader> {
auto surface =
ToolUtils::makeSurface(canvas, SkImageInfo::MakeN32Premul(kSize, kSize));
draw_mask(surface->getCanvas());
return surface->makeImageSnapshot()->makeShader(
SkTileMode::kClamp, SkTileMode::kClamp, &lm);
},
[](SkCanvas*, const SkMatrix& lm) -> sk_sp<SkShader> {
SkPictureRecorder recorder;
draw_mask(recorder.beginRecording(kSize, kSize));
return recorder.finishRecordingAsPicture()->makeShader(
SkTileMode::kClamp,
SkTileMode::kClamp,
&lm,
nullptr);
},
};
struct Config {
SkMatrix fCanvasMatrix,
fMaskMatrix,
fShaderMatrix;
} gConfigs[] = {
{ SkMatrix::I(), SkMatrix::MakeScale(2, 2), SkMatrix::MakeTrans(10, 10) },
{ SkMatrix::MakeScale(2, 2), SkMatrix::I(), SkMatrix::MakeTrans(10, 10) },
{ SkMatrix::MakeScale(2, 2), SkMatrix::MakeTrans(10, 10), SkMatrix::I() },
{ SkMatrix::Concat(SkMatrix::MakeScale(2, 2), SkMatrix::MakeTrans(10, 10)),
SkMatrix::I(), SkMatrix::I() },
{ SkMatrix::I(),
SkMatrix::Concat(SkMatrix::MakeScale(2, 2), SkMatrix::MakeTrans(10, 10)),
SkMatrix::I() },
{ SkMatrix::I(), SkMatrix::I(),
SkMatrix::Concat(SkMatrix::MakeScale(2, 2), SkMatrix::MakeTrans(10, 10)) },
};
SkPaint paint, rectPaint;
paint.setColor(0xff00ff00);
rectPaint.setStyle(SkPaint::kStroke_Style);
rectPaint.setColor(0xffff0000);
for (const auto& sm : gShaderMakers) {
{
SkAutoCanvasRestore acr(canvas, true);
for (const auto& cfg : gConfigs) {
paint.setMaskFilter(SkShaderMaskFilter::Make(sm(canvas, cfg.fShaderMatrix))
->makeWithMatrix(cfg.fMaskMatrix));
auto dest = SkRect::MakeWH(kSize, kSize);
SkMatrix::Concat(cfg.fMaskMatrix, cfg.fShaderMatrix).mapRect(&dest);
{
SkAutoCanvasRestore acr(canvas, true);
canvas->concat(cfg.fCanvasMatrix);
canvas->drawRect(dest, paint);
canvas->drawRect(dest, rectPaint);
}
canvas->translate(kSize * 2.5f, 0);
}
}
canvas->translate(0, kSize * 2.5f);
}
}