Fix downsampled blur with clamp mode.

Ensure the extra border added to the downsampled image doesn't filter in
values from outside the source bounds.

Bug: chromium:1174354
Change-Id: I6c62eeaa57ce4e5341eab24985553f87ab0df666
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/378322
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2021-03-02 14:28:21 -05:00 committed by Skia Commit-Bot
parent e1a728416c
commit 6d25f9d068
3 changed files with 100 additions and 34 deletions

57
gm/crbug_1174354.cpp Normal file
View File

@ -0,0 +1,57 @@
/*
* Copyright 2021 Google LLC
*
* 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/SkPaint.h"
#include "include/core/SkRect.h"
#include "include/effects/SkGradientShader.h"
#include "include/effects/SkImageFilters.h"
static void draw_bg_blur(SkCanvas* canvas, SkIRect rect, float sigma) {
// First create an intermediate layer that has an opaque area that we blur with transparency
// all around it. We want to make sure the transparency doesn't affect the blur of the opaque
// content.
auto outsetRect = SkRect::Make(rect).makeOutset(10, 10);
canvas->saveLayer(outsetRect, nullptr);
SkColor colors[] = {SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN};
float cx = (rect.left() + rect.right() )/2.f;
float cy = (rect.top() + rect.bottom())/2.f;
auto g = SkGradientShader::MakeSweep(cx,
cy,
colors,
nullptr,
3,
SkTileMode::kMirror,
0,
45,
0,
nullptr);
SkPaint paint;
paint.setShader(std::move(g));
canvas->drawRect(SkRect::Make(rect), paint);
// Now do the bg-blur into another save-layer that should only read the opaque region.
SkCanvas::SaveLayerRec rec;
auto blur = SkImageFilters::Blur(sigma, sigma, SkTileMode::kClamp, nullptr, rect);
rec.fBounds = &outsetRect;
rec.fBackdrop = blur.get();
canvas->saveLayer(rec);
canvas->restore();
canvas->restore();
}
DEF_SIMPLE_GM(crbug_1174354, canvas, 70, 250) {
// The initial fix for crbug.com/1156805 had an issue where the border added to the downsampled
// texture that was used as input to the blur could actually bring in transparency when there
// wasn't any within the original src bounds. It was populated the border using a filtering draw
// from the full res source that could read from outside the pixels surrounding the original src
// bounds.
draw_bg_blur(canvas, SkIRect::MakeXYWH(10, 10, 50, 50), 5);
draw_bg_blur(canvas, SkIRect::MakeXYWH(10, 70, 50, 50), 15);
draw_bg_blur(canvas, SkIRect::MakeXYWH(10, 130, 50, 50), 30);
draw_bg_blur(canvas, SkIRect::MakeXYWH(10, 190, 50, 50), 70);
}

View File

@ -118,6 +118,7 @@ gm_sources = [
"$_gm/crbug_1162942.cpp",
"$_gm/crbug_1167277.cpp",
"$_gm/crbug_1174186.cpp",
"$_gm/crbug_1174354.cpp",
"$_gm/crbug_1177833.cpp",
"$_gm/crbug_224618.cpp",
"$_gm/crbug_691386.cpp",

View File

@ -594,48 +594,56 @@ std::unique_ptr<GrSurfaceDrawContext> GaussianBlur(GrRecordingContext* context,
// Rather than run a potentially multi-pass rescaler on single rows/columns we just do a
// single bilerp draw. If we find this quality unacceptable we should think more about how
// to rescale these with better quality but without 4 separate multi-pass downscales.
// These all batch together into a single draw (and with the above rescaling when there
// is only one pass in the interior rescale).
auto cheapDownscale = [&](SkIRect dstRect, SkRect srcRect) {
rescaledSDC->drawTexture(nullptr,
srcCtx->readSurfaceView(),
srcAlphaType,
GrSamplerState::Filter::kLinear,
GrSamplerState::MipmapMode::kNone,
SkBlendMode::kSrc,
SK_PMColor4fWHITE,
srcRect,
SkRect::Make(dstRect),
GrAA::kNo,
GrQuadAAFlags::kNone,
SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint,
SkMatrix::I(),
nullptr);
auto cheapDownscale = [&](SkIRect dstRect, SkIRect srcRect) {
rescaledSDC->drawTexture(nullptr,
srcCtx->readSurfaceView(),
srcAlphaType,
GrSamplerState::Filter::kLinear,
GrSamplerState::MipmapMode::kNone,
SkBlendMode::kSrc,
SK_PMColor4fWHITE,
SkRect::Make(srcRect),
SkRect::Make(dstRect),
GrAA::kNo,
GrQuadAAFlags::kNone,
SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint,
SkMatrix::I(),
nullptr);
};
auto [dw, dh] = rescaledSDC->dimensions();
auto [dw, dh] = rescaledSize;
// The are the src rows and columns from the source that we will scale into the dst padding.
float sLCol = srcBounds.left();
float sTRow = srcBounds.top();
float sRCol = srcBounds.right() - 1;
float sBRow = srcBounds.bottom() - 1;
// Calculate src offsets and lengths for y when copying a col and x when copying a row.
float isx = 1.f/scaleX;
float isy = 1.f/scaleY;
float sx = srcBounds.left() - isx;
float sy = srcBounds.top() - isy;
float sw = srcBounds.width() + 2*isx;
float sh = srcBounds.height() + 2*isy;
int sx = srcBounds.left();
int sy = srcBounds.top();
int sw = srcBounds.width();
int sh = srcBounds.height();
// We double hit the four corners (last hit wins) rather than complicate the rects here.
cheapDownscale(SkIRect::MakeXYWH( 0, 0, 1, dh),
SkRect::MakeXYWH( sLCol, sy, 1, sh));
cheapDownscale(SkIRect::MakeXYWH( 0, 0, dw, 1),
SkRect::MakeXYWH( sx, sTRow, sw, 1));
cheapDownscale(SkIRect::MakeXYWH(dw - 1, 0, 1, dh),
SkRect::MakeXYWH( sRCol, sy, 1, sh));
cheapDownscale(SkIRect::MakeXYWH( 0, dh - 1, dw, 1),
SkRect::MakeXYWH( sx, sBRow, sw, 1));
// Downscale the edges from the original source. These draws should batch together (and with
// the above interior rescaling when it is a single pass).
cheapDownscale(SkIRect::MakeXYWH( 0, 1, 1, dh),
SkIRect::MakeXYWH( sLCol, sy, 1, sh));
cheapDownscale(SkIRect::MakeXYWH( 1, 0, dw, 1),
SkIRect::MakeXYWH( sx, sTRow, sw, 1));
cheapDownscale(SkIRect::MakeXYWH(dw + 1, 1, 1, dh),
SkIRect::MakeXYWH( sRCol, sy, 1, sh));
cheapDownscale(SkIRect::MakeXYWH( 1, dh + 1, dw, 1),
SkIRect::MakeXYWH( sx, sBRow, sw, 1));
// Copy the corners from the original source. These would batch with the edges except that
// at time of writing we recognize these can use kNearest and downgrade the filter. So they
// batch with each other but not the edge draws.
cheapDownscale(SkIRect::MakeXYWH( 0, 0, 1, 1),
SkIRect::MakeXYWH(sLCol, sTRow, 1, 1));
cheapDownscale(SkIRect::MakeXYWH(dw + 1, 0, 1, 1),
SkIRect::MakeXYWH(sRCol, sTRow, 1, 1));
cheapDownscale(SkIRect::MakeXYWH(dw + 1,dh + 1, 1, 1),
SkIRect::MakeXYWH(sRCol, sBRow, 1, 1));
cheapDownscale(SkIRect::MakeXYWH( 0, dh + 1, 1, 1),
SkIRect::MakeXYWH(sLCol, sBRow, 1, 1));
}
srcView = rescaledSDC->readSurfaceView();
// Drop the contexts so we don't hold the proxies longer than necessary.