SkGpuBlurUtils::GaussianBlur handles degenerate downscale cases.
Previously it aborted if at a downscale to 1 pixel the effective sigma was greater than the max sigma. For repeat/mirror the single col/row is the blurred result For decal/clamp add a border of transparent/original border and then recurse. Bug: skia:11735 Bug: chromium:1174354 Change-Id: I3438f49fdc5167f6def61a49f9fc8eaef3912ec3 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/385279 Commit-Queue: Brian Salomon <bsalomon@google.com> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
parent
38517c2c39
commit
3079266f92
@ -14,12 +14,10 @@
|
||||
#include "include/gpu/GrRecordingContext.h"
|
||||
#include "src/gpu/GrCaps.h"
|
||||
#include "src/gpu/GrRecordingContextPriv.h"
|
||||
#include "src/gpu/SkGr.h"
|
||||
#include "src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h"
|
||||
#include "src/gpu/effects/GrMatrixConvolutionEffect.h"
|
||||
|
||||
#include "src/gpu/SkGr.h"
|
||||
|
||||
|
||||
using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
|
||||
|
||||
static void fill_in_2D_gaussian_kernel(float* kernel, int width, int height,
|
||||
@ -59,35 +57,37 @@ static void fill_in_2D_gaussian_kernel(float* kernel, int width, int height,
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws 'rtcRect' into 'surfaceDrawContext' evaluating a 1D Gaussian over 'srcView'. The src rect
|
||||
* is 'rtcRect' offset by 'rtcToSrcOffset'. 'mode' and 'bounds' are applied to the src coords.
|
||||
* Draws 'dstRect' into 'surfaceFillContext' evaluating a 1D Gaussian over 'srcView'. The src rect
|
||||
* is 'dstRect' offset by 'dstToSrcOffset'. 'mode' and 'bounds' are applied to the src coords.
|
||||
*/
|
||||
static void convolve_gaussian_1d(GrSurfaceDrawContext* surfaceDrawContext,
|
||||
static void convolve_gaussian_1d(GrSurfaceFillContext* sfc,
|
||||
GrSurfaceProxyView srcView,
|
||||
const SkIRect srcSubset,
|
||||
SkIVector rtcToSrcOffset,
|
||||
const SkIRect& rtcRect,
|
||||
SkIVector dstToSrcOffset,
|
||||
const SkIRect& dstRect,
|
||||
SkAlphaType srcAlphaType,
|
||||
Direction direction,
|
||||
int radius,
|
||||
float sigma,
|
||||
SkTileMode mode) {
|
||||
SkASSERT(radius && !SkGpuBlurUtils::IsEffectivelyZeroSigma(sigma));
|
||||
GrPaint paint;
|
||||
auto wm = SkTileModeToWrapMode(mode);
|
||||
auto srcRect = rtcRect.makeOffset(rtcToSrcOffset);
|
||||
|
||||
auto srcRect = dstRect.makeOffset(dstToSrcOffset);
|
||||
// NOTE: This could just be GrMatrixConvolutionEffect with one of the dimensions set to 1
|
||||
// and the appropriate kernel already computed, but there's value in keeping the shader simpler.
|
||||
// TODO(michaelludwig): Is this true? If not, is the shader key simplicity worth it two have
|
||||
// two convolution effects?
|
||||
std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make(
|
||||
std::move(srcView), srcAlphaType, direction, radius, sigma, wm, srcSubset, &srcRect,
|
||||
*surfaceDrawContext->caps()));
|
||||
paint.setColorFragmentProcessor(std::move(conv));
|
||||
paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
|
||||
surfaceDrawContext->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
|
||||
SkRect::Make(rtcRect), SkRect::Make(srcRect));
|
||||
std::unique_ptr<GrFragmentProcessor> conv =
|
||||
GrGaussianConvolutionFragmentProcessor::Make(std::move(srcView),
|
||||
srcAlphaType,
|
||||
direction,
|
||||
radius,
|
||||
sigma,
|
||||
wm,
|
||||
srcSubset,
|
||||
&srcRect,
|
||||
*sfc->caps());
|
||||
sfc->fillRectToRectWithFP(srcRect, dstRect, std::move(conv));
|
||||
}
|
||||
|
||||
static std::unique_ptr<GrSurfaceDrawContext> convolve_gaussian_2d(GrRecordingContext* context,
|
||||
@ -504,6 +504,9 @@ std::unique_ptr<GrSurfaceDrawContext> GaussianBlur(GrRecordingContext* context,
|
||||
auto result = GrSurfaceDrawContext::Make(context, srcColorType, std::move(colorSpace), fit,
|
||||
dstBounds.size(), 1, GrMipmapped::kNo,
|
||||
srcView.proxy()->isProtected(), srcView.origin());
|
||||
if (!result) {
|
||||
return nullptr;
|
||||
}
|
||||
GrSamplerState sampler(SkTileModeToWrapMode(mode), GrSamplerState::Filter::kNearest);
|
||||
auto fp = GrTextureEffect::MakeSubset(std::move(srcView),
|
||||
srcAlphaType,
|
||||
@ -536,26 +539,23 @@ std::unique_ptr<GrSurfaceDrawContext> GaussianBlur(GrRecordingContext* context,
|
||||
radiusX, radiusY, mode, fit);
|
||||
}
|
||||
|
||||
GrColorInfo colorInfo(srcColorType, srcAlphaType, colorSpace);
|
||||
auto srcCtx = GrSurfaceContext::Make(context, srcView, colorInfo);
|
||||
SkASSERT(srcCtx);
|
||||
|
||||
float scaleX = sigmaX > kMaxSigma ? kMaxSigma/sigmaX : 1.f;
|
||||
float scaleY = sigmaY > kMaxSigma ? kMaxSigma/sigmaY : 1.f;
|
||||
|
||||
// We round down here so that when we recalculate sigmas we know they will be below
|
||||
// MAX_BLUR_SIGMA.
|
||||
SkISize rescaledSize = {sk_float_floor2int(srcBounds.width() *scaleX),
|
||||
sk_float_floor2int(srcBounds.height()*scaleY)};
|
||||
if (rescaledSize.isEmpty()) {
|
||||
// TODO: Handle this degenerate case.
|
||||
return nullptr;
|
||||
}
|
||||
// Compute the sigmas using the actual scale factors used once we integerized the rescaledSize.
|
||||
// kMaxSigma (but clamp to 1 do we don't have an empty texture).
|
||||
SkISize rescaledSize = {std::max(sk_float_floor2int(srcBounds.width() *scaleX), 1),
|
||||
std::max(sk_float_floor2int(srcBounds.height()*scaleY), 1)};
|
||||
// Compute the sigmas using the actual scale factors used once we integerized the
|
||||
// rescaledSize.
|
||||
scaleX = static_cast<float>(rescaledSize.width()) /srcBounds.width();
|
||||
scaleY = static_cast<float>(rescaledSize.height())/srcBounds.height();
|
||||
sigmaX *= scaleX;
|
||||
sigmaY *= scaleY;
|
||||
|
||||
GrColorInfo colorInfo(srcColorType, srcAlphaType, colorSpace);
|
||||
auto srcCtx = GrSurfaceContext::Make(context, srcView, colorInfo);
|
||||
SkASSERT(srcCtx);
|
||||
// When we are in clamp mode any artifacts in the edge pixels due to downscaling may be
|
||||
// exacerbated because of the tile mode. The particularly egregious case is when the original
|
||||
// image has transparent black around the edges and the downscaling pulls in some non-zero
|
||||
@ -569,47 +569,57 @@ std::unique_ptr<GrSurfaceDrawContext> GaussianBlur(GrRecordingContext* context,
|
||||
// border of extra pixels is used as the edge pixels for clamp mode but the dest bounds
|
||||
// corresponds only to the pixels inside the border (the normally rescaled pixels inside this
|
||||
// border).
|
||||
int pad = mode == SkTileMode::kClamp ? 1 : 0;
|
||||
// Moreover, if we clamped the rescaled size to 1 column or row then we still have a sigma
|
||||
// that is greater than kMaxSigma. By using a pad and making the src 3 wide/tall instead of
|
||||
// 1 we can recurse again and do another downscale. Since mirror and repeat modes are trivial
|
||||
// for a single col/row we only add padding based on sigma exceeding kMaxSigma for decal.
|
||||
int padX = mode == SkTileMode::kClamp ||
|
||||
(mode == SkTileMode::kDecal && sigmaX > kMaxSigma) ? 1 : 0;
|
||||
int padY = mode == SkTileMode::kClamp ||
|
||||
(mode == SkTileMode::kDecal && sigmaY > kMaxSigma) ? 1 : 0;
|
||||
auto rescaledSDC = GrSurfaceDrawContext::Make(
|
||||
srcCtx->recordingContext(),
|
||||
colorInfo.colorType(),
|
||||
colorInfo.refColorSpace(),
|
||||
SkBackingFit::kApprox,
|
||||
{rescaledSize.width() + 2*pad, rescaledSize.height() + 2*pad},
|
||||
{rescaledSize.width() + 2*padX, rescaledSize.height() + 2*padY},
|
||||
1,
|
||||
GrMipmapped::kNo,
|
||||
srcCtx->asSurfaceProxy()->isProtected(),
|
||||
srcCtx->origin());
|
||||
|
||||
if (!rescaledSDC) {
|
||||
return nullptr;
|
||||
}
|
||||
if ((padX || padY) && mode == SkTileMode::kDecal) {
|
||||
rescaledSDC->clear(SkPMColor4f{0, 0, 0, 0});
|
||||
}
|
||||
if (!srcCtx->rescaleInto(rescaledSDC.get(),
|
||||
SkIRect::MakeSize(rescaledSize).makeOffset(pad, pad),
|
||||
SkIRect::MakeSize(rescaledSize).makeOffset(padX, padY),
|
||||
srcBounds,
|
||||
SkSurface::RescaleGamma::kSrc,
|
||||
SkSurface::RescaleMode::kRepeatedLinear)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (pad) {
|
||||
if (mode == SkTileMode::kClamp) {
|
||||
SkASSERT(padX == 1 && padY == 1);
|
||||
// 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.
|
||||
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);
|
||||
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] = rescaledSize;
|
||||
// The are the src rows and columns from the source that we will scale into the dst padding.
|
||||
@ -660,7 +670,7 @@ std::unique_ptr<GrSurfaceDrawContext> GaussianBlur(GrRecordingContext* context,
|
||||
scaledDstBounds.fRight *= scaleX;
|
||||
scaledDstBounds.fBottom *= scaleY;
|
||||
// Account for padding in our rescaled src, if any.
|
||||
scaledDstBounds.offset(pad, pad);
|
||||
scaledDstBounds.offset(padX, padY);
|
||||
// Turn the scaled down dst bounds into an integer pixel rect.
|
||||
auto scaledDstBoundsI = scaledDstBounds.roundOut();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user