skia2/gm/savelayer.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

455 lines
16 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/gm.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorSpace.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/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/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypeface.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradientShader.h"
#include "include/effects/SkImageFilters.h"
#include "include/effects/SkShaderMaskFilter.h"
#include "include/utils/SkRandom.h"
#include "src/core/SkCanvasPriv.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include <string.h>
#include <initializer_list>
// This GM tests out the deprecated Android-specific unclipped saveLayer "feature".
// In particular, it attempts to compare the performance of unclipped saveLayers with alternatives.
static void save_layer_unclipped(SkCanvas* canvas,
SkScalar l, SkScalar t, SkScalar r, SkScalar b) {
SkPaint paint;
paint.setAlphaf(0.25f);
SkRect rect = SkRect::MakeLTRB(l, t, r, b);
canvas->saveLayer({ &rect, &paint, nullptr, nullptr, nullptr,
(SkCanvas::SaveLayerFlags) SkCanvasPriv::kDontClipToLayer_SaveLayerFlag });
}
static void do_draw(SkCanvas* canvas) {
SkPaint paint;
paint.setColor(0xFFFF0000);
for (int i = 0; i < 20; ++i) {
canvas->drawRect({ 15, 15, 290, 40 }, paint);
canvas->translate(0, 30);
}
}
class UnclippedSaveLayerGM : public skiagm::GM {
public:
UnclippedSaveLayerGM() { this->setBGColor(SK_ColorWHITE); }
protected:
bool runAsBench() const override { return true; }
SkString onShortName() override {
return SkString("savelayer_unclipped");
}
SkISize onISize() override { return SkISize::Make(320, 640); }
void onDraw(SkCanvas* canvas) override {
const SkScalar L = 10;
const SkScalar T = 10;
const SkScalar R = 310;
const SkScalar B = 630;
canvas->clipRect({ L, T, R, B });
SkAutoCanvasRestore acr(canvas, true);
save_layer_unclipped(canvas, L, T, R, T + 100);
save_layer_unclipped(canvas, L, B - 100, R, B);
do_draw(canvas);
}
private:
typedef skiagm::GM INHERITED;
};
DEF_GM(return new UnclippedSaveLayerGM;)
DEF_SIMPLE_GM(picture_savelayer, canvas, 320, 640) {
SkPaint paint1, paint2, paint3;
paint1.setAlphaf(0.5f);
paint2.setAlphaf(0.25f);
paint3.setColor(0xFFFF0000);
SkRect rect1{40, 5, 80, 70}, rect2{5, 40, 70, 80}, rect3{10, 10, 70, 70};
// In the future, we might also test the clipped case by allowing i = 0
for(int i = 1; i < 2; ++i) {
canvas->translate(100 * i, 0);
auto flag = i ?
(SkCanvas::SaveLayerFlags) SkCanvasPriv::kDontClipToLayer_SaveLayerFlag : 0;
canvas->saveLayer({ &rect1, &paint1, nullptr, nullptr, nullptr, flag});
canvas->saveLayer({ &rect2, &paint2, nullptr, nullptr, nullptr, flag});
canvas->drawRect(rect3, paint3);
canvas->restore();
canvas->restore();
}
};
// Test kInitWithPrevious_SaveLayerFlag by drawing an image, save a layer with the flag, which
// should seed the layer with the image (from below). Then we punch a hole in the layer and
// restore with kPlus mode, which should show the mandrill super-bright on the outside, but
// normal where we punched the hole.
DEF_SIMPLE_GM(savelayer_initfromprev, canvas, 256, 256) {
canvas->drawImage(GetResourceAsImage("images/mandrill_256.png"), 0, 0, nullptr);
SkCanvas::SaveLayerRec rec;
SkPaint paint;
paint.setBlendMode(SkBlendMode::kPlus);
rec.fSaveLayerFlags = SkCanvas::kInitWithPrevious_SaveLayerFlag;
rec.fPaint = &paint;
canvas->saveLayer(rec);
paint.setBlendMode(SkBlendMode::kClear);
canvas->drawCircle(128, 128, 96, paint);
canvas->restore();
};
static void draw_mask(SkCanvas* canvas, int size) {
const SkScalar cx = size * SK_ScalarHalf,
cy = cx;
const SkColor colors[] = { 0x00000000, 0xffff0000, 0x00000000, 0xffff0000, 0x00000000,
0xffff0000, 0x00000000, 0xffff0000, 0x00000000 };
SkPaint paint;
paint.setAntiAlias(true);
paint.setShader(SkGradientShader::MakeSweep(cx, cy, colors, nullptr, SK_ARRAY_COUNT(colors)));
canvas->drawPaint(paint);
paint.setShader(SkGradientShader::MakeRadial({cx, cy}, size / 4, colors, nullptr, 2,
SkTileMode::kClamp));
canvas->drawCircle(cx, cy, size / 4, paint);
}
DEF_SIMPLE_GM(savelayer_clipmask, canvas, 1200, 1200) {
static constexpr int kSize = 100;
static constexpr SkRect kLayerBounds = { kSize * 0.25f, kSize * 0.25f,
kSize * 0.75f, kSize * 0.75f };
static constexpr struct {
const SkRect* bounds;
const SkScalar matrix[9];
} kConfigs[] = {
{ nullptr, { 1 , 0 , 0, 0 , 1 , 0, 0, 0, 1 } },
{ nullptr, { 2 , 0 , 0, 0 , 2 , 0, 0, 0, 1 } },
{ nullptr, { 2 , 0 , -50, 0 , 2 , -50, 0, 0, 1 } },
{ nullptr, { 0.707f, -0.707f, 50, 0.707f, 0.707f, -20, 0, 0, 1 } },
{ nullptr, { 0.5f , 0 , 25, 0 , 0.5f , 25, 0, 0, 1 } },
{ &kLayerBounds, { 1 , 0 , 0, 0 , 1 , 0, 0, 0, 1 } },
{ &kLayerBounds, { 2 , 0 , 0, 0 , 2 , 0, 0, 0, 1 } },
{ &kLayerBounds, { 2 , 0 , -50, 0 , 2 , -50, 0, 0, 1 } },
{ &kLayerBounds, { 0.707f, -0.707f, 50, 0.707f, 0.707f, -20, 0, 0, 1 } },
{ &kLayerBounds, { 0.5f , 0 , 25, 0 , 0.5f , 25, 0, 0, 1 } },
};
using MaskMakerFunc = sk_sp<SkImage> (*)(int size);
static const MaskMakerFunc kMaskMakers[] = {
[](int size) -> sk_sp<SkImage> {
auto surf = SkSurface::MakeRaster(SkImageInfo::MakeA8(size, size));
draw_mask(surf->getCanvas(), size);
return surf->makeImageSnapshot();
},
[](int size) -> sk_sp<SkImage> {
auto surf = SkSurface::MakeRasterN32Premul(size, size);
draw_mask(surf->getCanvas(), size);
return surf->makeImageSnapshot();
},
[](int size) -> sk_sp<SkImage> {
SkPictureRecorder recorder;
draw_mask(recorder.beginRecording(size, size), size);
return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(),
SkISize::Make(size, size),
nullptr, nullptr,
SkImage::BitDepth::kU8,
SkColorSpace::MakeSRGB());
}
};
using PaintMakerFunc = SkPaint (*)();
static const PaintMakerFunc kPaintMakers[] = {
[]() -> SkPaint { return SkPaint(); },
[]() -> SkPaint {
SkPaint p; p.setImageFilter(SkImageFilters::Blur(2, 2, nullptr)); return p;
},
[]() -> SkPaint { SkPaint p; p.setBlendMode(SkBlendMode::kSrcOut); return p; },
};
canvas->drawColor(0xffcccccc);
SkMatrix clipMatrix;
SkCanvas::SaveLayerRec rec;
rec.fClipMatrix = &clipMatrix;
for (const auto& paintMaker : kPaintMakers) {
auto layerPaint = paintMaker();
rec.fPaint = &layerPaint;
for (const auto& maskMaker : kMaskMakers) {
sk_sp<SkImage> mask = maskMaker(kSize);
rec.fClipMask = mask.get();
canvas->save();
for (const auto cfg : kConfigs) {
rec.fBounds = cfg.bounds;
clipMatrix.set9(cfg.matrix);
canvas->saveLayer(rec);
SkPaint paint;
paint.setColor(0xff0000ff);
canvas->drawRect(SkRect::MakeWH(50, 50), paint);
paint.setColor(0xffff0000);
canvas->drawRect(SkRect::MakeXYWH(50, 0, 50, 50), paint);
paint.setColor(0xff00ff00);
canvas->drawRect(SkRect::MakeXYWH(50, 50, 50, 50), paint);
paint.setColor(0xffffff00);
canvas->drawRect(SkRect::MakeXYWH(0, 50, 50, 50), paint);
canvas->restore();
canvas->translate(120, 0);
}
canvas->restore();
canvas->translate(0, 120);
}
}
}
DEF_SIMPLE_GM(savelayer_coverage, canvas, 500, 500) {
canvas->saveLayer(nullptr, nullptr);
SkRect r = { 0, 0, 200, 200 };
SkPaint layerPaint;
layerPaint.setBlendMode(SkBlendMode::kModulate);
auto image = GetResourceAsImage("images/mandrill_128.png");
auto proc = [layerPaint](SkCanvas* canvas, SkCanvas::SaveLayerRec& rec) {
SkPaint paint;
paint.setColor(SK_ColorRED);
canvas->saveLayer(rec);
canvas->drawCircle(100, 100, 50, paint);
paint.setColor(0x8800FF00);
canvas->drawRect({10, 90, 190, 110}, paint);
canvas->restore();
};
const int yflags[] = { 0, SkCanvas::kInitWithPrevious_SaveLayerFlag };
for (int y = 0; y <= 1; ++y) {
const int xflags[] = { 0, SkCanvas::kMaskAgainstCoverage_EXPERIMENTAL_DONT_USE_SaveLayerFlag };
for (int x = 0; x <= 1; ++x) {
canvas->save();
canvas->translate(x * 200.f, y * 200.f);
SkCanvas::SaveLayerRec rec(&r, &layerPaint, yflags[y] | xflags[x]);
canvas->drawImageRect(image, r, nullptr);
proc(canvas, rec);
canvas->restore();
}
}
canvas->restore();
}
DEF_SIMPLE_GM(savelayer_clipmask_maskfilter, canvas, 500, 500) {
// Offscreen surface for making the clip mask and mask filter images
auto surf = SkSurface::MakeRaster(SkImageInfo::MakeA8(100, 100));
SkPaint maskPaint;
maskPaint.setColor(SK_ColorWHITE);
maskPaint.setAntiAlias(true);
// Draw a centered circle for the mask filter
surf->getCanvas()->clear(SK_ColorTRANSPARENT);
surf->getCanvas()->drawCircle(50.f, 50.f, 50.f, maskPaint);
auto maskFilterImage = surf->makeImageSnapshot();
sk_sp<SkMaskFilter> maskFilter = SkShaderMaskFilter::Make(maskFilterImage->makeShader());
// Cut out a cross for the clip mask
surf->getCanvas()->clear(SK_ColorTRANSPARENT);
surf->getCanvas()->drawRect(SkRect::MakeLTRB(0.f, 0.f, 40.f, 40.f), maskPaint);
surf->getCanvas()->drawRect(SkRect::MakeLTRB(60.f, 0.f, 100.f, 40.f), maskPaint);
surf->getCanvas()->drawRect(SkRect::MakeLTRB(0.f, 60.f, 40.f, 100.f), maskPaint);
surf->getCanvas()->drawRect(SkRect::MakeLTRB(60.f, 60.f, 100.f, 100.f), maskPaint);
auto clipMaskImage = surf->makeImageSnapshot();
SkMatrix clipMatrix = SkMatrix::I();
SkRect clipBounds = SkRect::MakeWH(100, 100);
// On the main canvas, save a 100x100 layer three times, applying clip mask, mask filter, or
// both, translating across the GM for each configuration. Since the mask filter is provided
// on the layer restore paint, it must be ignored by the restore since coverage is not well
// defined.
canvas->clear(SK_ColorGRAY);
canvas->translate(25.f, 0.f);
// Clip mask only
SkCanvas::SaveLayerRec rec;
rec.fBounds = &clipBounds;
rec.fClipMask = clipMaskImage.get();
rec.fClipMatrix = &clipMatrix;
canvas->saveLayer(rec);
canvas->clear(SK_ColorWHITE);
canvas->restore();
canvas->translate(125.f, 0.f);
// Mask filter only (should be ignored, producing a white square)
maskPaint.setMaskFilter(maskFilter);
rec.fClipMask = nullptr;
rec.fPaint = &maskPaint;
canvas->saveLayer(rec);
canvas->clear(SK_ColorWHITE);
canvas->restore();
canvas->translate(125.f, 0.f);
// Both (mask filter is ignored, but clip mask should not, so should look like first draw)
rec.fClipMask = clipMaskImage.get();
canvas->saveLayer(rec);
canvas->clear(SK_ColorWHITE);
canvas->restore();
}
static void draw_cell(SkCanvas* canvas, sk_sp<SkTextBlob> blob, SkColor c, SkScalar w, SkScalar h,
bool useDrawBehind) {
SkRect r = SkRect::MakeWH(w, h);
SkPaint p;
p.setColor(c);
p.setBlendMode(SkBlendMode::kSrc);
canvas->drawRect(r, p);
p.setBlendMode(SkBlendMode::kSrcOver);
const SkScalar margin = 80;
r.fLeft = w - margin;
// save the behind image
SkDEBUGCODE(int sc0 =) canvas->getSaveCount();
SkDEBUGCODE(int sc1 =) SkCanvasPriv::SaveBehind(canvas, &r);
SkDEBUGCODE(int sc2 =) canvas->getSaveCount();
SkASSERT(sc0 == sc1);
SkASSERT(sc0 + 1 == sc2);
// draw the foreground (including over the 'behind' section)
p.setColor(SK_ColorBLACK);
canvas->drawTextBlob(blob, 10, 30, p);
// draw the treatment
const SkPoint pts[] = { {r.fLeft,0}, {r.fRight, 0} };
const SkColor colors[] = { 0x88000000, 0x0 };
auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
p.setShader(sh);
p.setBlendMode(SkBlendMode::kDstIn);
if (useDrawBehind) {
SkCanvasPriv::DrawBehind(canvas, p);
} else {
canvas->drawRect(r, p);
}
// this should restore the behind image
canvas->restore();
SkDEBUGCODE(int sc3 =) canvas->getSaveCount();
SkASSERT(sc3 == sc0);
// just outline where we expect the treatment to appear
p.reset();
p.setStyle(SkPaint::kStroke_Style);
p.setAlphaf(0.25f);
}
static void draw_list(SkCanvas* canvas, sk_sp<SkTextBlob> blob, bool useDrawBehind) {
SkAutoCanvasRestore acr(canvas, true);
SkRandom rand;
SkScalar w = 400;
SkScalar h = 40;
for (int i = 0; i < 8; ++i) {
SkColor c = rand.nextU(); // ensure we're opaque
c = (c & 0xFFFFFF) | 0x80000000;
draw_cell(canvas, blob, c, w, h, useDrawBehind);
canvas->translate(0, h);
}
}
DEF_SIMPLE_GM(save_behind, canvas, 830, 670) {
SkFont font;
font.setTypeface(ToolUtils::create_portable_typeface());
font.setSize(30);
const char text[] = "This is a very long line of text";
auto blob = SkTextBlob::MakeFromText(text, strlen(text), font);
for (bool useDrawBehind : {false, true}) {
canvas->save();
draw_list(canvas, blob, useDrawBehind);
canvas->translate(0, 350);
canvas->saveLayer({0, 0, 400, 320}, nullptr);
draw_list(canvas, blob, useDrawBehind);
canvas->restore();
canvas->restore();
canvas->translate(430, 0);
}
}
#include "include/effects/SkGradientShader.h"
DEF_SIMPLE_GM(savelayer_f16, canvas, 900, 300) {
int n = 15;
SkRect r{0, 0, 300, 300};
SkPaint paint;
const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED };
paint.setShader(SkGradientShader::MakeSweep(r.centerX(), r.centerY(),
colors, nullptr, SK_ARRAY_COUNT(colors)));
canvas->drawOval(r, paint);
paint.setAlphaf(1.0f/n);
paint.setBlendMode(SkBlendMode::kPlus);
for (auto flags : {0, (int)SkCanvas::kF16ColorType}) {
canvas->translate(r.width(), 0);
SkCanvas::SaveLayerRec rec;
rec.fSaveLayerFlags = flags;
canvas->saveLayer(rec);
for (int i = 0; i < n; ++i) {
canvas->drawOval(r, paint);
}
canvas->restore();
}
}