diff options
author | 2017-06-30 10:44:45 -0700 | |
---|---|---|
committer | 2017-06-30 18:11:16 +0000 | |
commit | 039a7c70cea29aa40c4fb880b0d6bb523d449568 (patch) | |
tree | b62a5274d4b1cbcaac89ad5fb8bf86118c7caa87 /src/core | |
parent | 981deec8b2928f07e3899849bed31bfbb8c19b9b (diff) |
Added new edge handling mode (clamp and repeat) to Gaussian blur filter.
Gaussian blur filter will interpolate value by using out of bounds
coords, which is 0. This makes it appears darker near the bounds in the
blurred images. There are two issues: 1) when downsampling and
upsampling, we should use GrTextureDomainEffect kClamp_Mode to clamp
the texture coords to the bounds; 2) during Gaussian blur, we need to
clamp to texture bounds.
BUG=622128
TEST=cc_unittests, GM image test & manual. Some test results can be found at:
https://bugs.chromium.org/p/chromium/issues/detail?id=622128#c49
Change-Id: I9283da1d91efb0da94a991f2d372e9f62c288bdc
Reviewed-on: https://skia-review.googlesource.com/20465
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Stephen White <senorblanco@chromium.org>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/SkBlurImageFilter.cpp | 45 | ||||
-rw-r--r-- | src/core/SkGpuBlurUtils.cpp | 99 | ||||
-rw-r--r-- | src/core/SkGpuBlurUtils.h | 20 |
3 files changed, 108 insertions, 56 deletions
diff --git a/src/core/SkBlurImageFilter.cpp b/src/core/SkBlurImageFilter.cpp index 6c18962fba..428b9375fb 100644 --- a/src/core/SkBlurImageFilter.cpp +++ b/src/core/SkBlurImageFilter.cpp @@ -26,7 +26,8 @@ public: SkBlurImageFilterImpl(SkScalar sigmaX, SkScalar sigmaY, sk_sp<SkImageFilter> input, - const CropRect* cropRect); + const CropRect* cropRect, + SkBlurImageFilter::TileMode tileMode); SkRect computeFastBounds(const SkRect&) const override; @@ -42,6 +43,7 @@ protected: private: SkSize fSigma; + SkBlurImageFilter::TileMode fTileMode; typedef SkImageFilter INHERITED; friend class SkImageFilter; @@ -55,11 +57,13 @@ SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END sk_sp<SkImageFilter> SkBlurImageFilter::Make(SkScalar sigmaX, SkScalar sigmaY, sk_sp<SkImageFilter> input, - const SkImageFilter::CropRect* cropRect) { + const SkImageFilter::CropRect* cropRect, + TileMode tileMode) { if (0 == sigmaX && 0 == sigmaY && !cropRect) { return input; } - return sk_sp<SkImageFilter>(new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect)); + return sk_sp<SkImageFilter>( + new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect, tileMode)); } // This rather arbitrary-looking value results in a maximum box blur kernel size @@ -77,9 +81,12 @@ static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) { return sigma; } -SkBlurImageFilterImpl::SkBlurImageFilterImpl( - SkScalar sigmaX, SkScalar sigmaY, sk_sp<SkImageFilter> input, const CropRect* cropRect) - : INHERITED(&input, 1, cropRect), fSigma{sigmaX, sigmaY} {} +SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX, + SkScalar sigmaY, + sk_sp<SkImageFilter> input, + const CropRect* cropRect, + SkBlurImageFilter::TileMode tileMode) + : INHERITED(&input, 1, cropRect), fSigma{sigmaX, sigmaY}, fTileMode(tileMode) {} sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) { SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); @@ -94,6 +101,24 @@ void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const { buffer.writeScalar(fSigma.fHeight); } +#if SK_SUPPORT_GPU +static GrTextureDomain::Mode to_texture_domain_mode(SkBlurImageFilter::TileMode tileMode) { + switch (tileMode) { + case SkBlurImageFilter::TileMode::kIgnore_TileMode: + return GrTextureDomain::kIgnore_Mode; + case SkBlurImageFilter::TileMode::kClamp_TileMode: + return GrTextureDomain::kClamp_Mode; + case SkBlurImageFilter::TileMode::kClampToBlack_TileMode: + return GrTextureDomain::kDecal_Mode; + case SkBlurImageFilter::TileMode::kRepeat_TileMode: + return GrTextureDomain::kRepeat_Mode; + default: + SkFAIL("Unsupported tile mode."); + return GrTextureDomain::kDecal_Mode; + } +} +#endif + static void get_box3_params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset, int *highOffset) { float pi = SkScalarToFloat(SK_ScalarPI); @@ -165,9 +190,10 @@ sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* sourc std::move(inputTexture), sk_ref_sp(input->getColorSpace()), dstBounds, - &inputBounds, + inputBounds, sigma.x(), - sigma.y())); + sigma.y(), + to_texture_domain_mode(fTileMode))); if (!renderTargetContext) { return nullptr; } @@ -182,6 +208,7 @@ sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* sourc } #endif + // TODO: Implement CPU backend for different fTileMode. int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX; int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY; get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX); @@ -279,7 +306,7 @@ const { sk_sp<SkImageFilter> input = this->getInput(0)->makeColorSpace(xformer); return SkBlurImageFilter::Make(fSigma.width(), fSigma.height(), std::move(input), - this->getCropRectIfSet()); + this->getCropRectIfSet(), fTileMode); } SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const { diff --git a/src/core/SkGpuBlurUtils.cpp b/src/core/SkGpuBlurUtils.cpp index 40a6c24706..129a8fbfb7 100644 --- a/src/core/SkGpuBlurUtils.cpp +++ b/src/core/SkGpuBlurUtils.cpp @@ -74,13 +74,13 @@ static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext, Gr1DKernelEffect::Direction direction, int radius, float sigma, - bool useBounds, + GrTextureDomain::Mode mode, int bounds[2]) { GrPaint paint; paint.setGammaCorrect(renderTargetContext->isGammaCorrect()); sk_sp<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make( - std::move(proxy), direction, radius, sigma, useBounds, bounds)); + std::move(proxy), direction, radius, sigma, mode, bounds)); paint.addColorFragmentProcessor(std::move(conv)); paint.setPorterDuffXPFactory(SkBlendMode::kSrc); SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()), @@ -98,19 +98,18 @@ static void convolve_gaussian_2d(GrRenderTargetContext* renderTargetContext, int radiusY, SkScalar sigmaX, SkScalar sigmaY, - const SkIRect* srcBounds) { + const SkIRect& srcBounds, + GrTextureDomain::Mode mode) { SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()), -SkIntToScalar(srcOffset.y())); SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); GrPaint paint; paint.setGammaCorrect(renderTargetContext->isGammaCorrect()); - SkIRect bounds = srcBounds ? *srcBounds : SkIRect::EmptyIRect(); sk_sp<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::MakeGaussian( - std::move(proxy), bounds, size, 1.0, 0.0, kernelOffset, - srcBounds ? GrTextureDomain::kDecal_Mode : GrTextureDomain::kIgnore_Mode, - true, sigmaX, sigmaY)); + std::move(proxy), srcBounds, size, 1.0, 0.0, kernelOffset, + mode, true, sigmaX, sigmaY)); paint.addColorFragmentProcessor(std::move(conv)); paint.setPorterDuffXPFactory(SkBlendMode::kSrc); renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), @@ -124,21 +123,23 @@ static void convolve_gaussian(GrRenderTargetContext* renderTargetContext, Gr1DKernelEffect::Direction direction, int radius, float sigma, - const SkIRect* srcBounds, - const SkIPoint& srcOffset) { + const SkIRect& srcBounds, + const SkIPoint& srcOffset, + GrTextureDomain::Mode mode) { int bounds[2] = { 0, 0 }; SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height()); - if (!srcBounds) { + if (GrTextureDomain::kIgnore_Mode == mode) { convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset, - std::move(proxy), direction, radius, sigma, false, bounds); + std::move(proxy), direction, radius, sigma, + GrTextureDomain::kIgnore_Mode, bounds); return; } - SkIRect midRect = *srcBounds, leftRect, rightRect; + SkIRect midRect = srcBounds, leftRect, rightRect; midRect.offset(srcOffset); SkIRect topRect, bottomRect; if (direction == Gr1DKernelEffect::kX_Direction) { - bounds[0] = srcBounds->left(); - bounds[1] = srcBounds->right(); + bounds[0] = srcBounds.left(); + bounds[1] = srcBounds.right(); topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top()); bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom()); midRect.inset(radius, 0); @@ -148,8 +149,8 @@ static void convolve_gaussian(GrRenderTargetContext* renderTargetContext, dstRect.fTop = midRect.top(); dstRect.fBottom = midRect.bottom(); } else { - bounds[0] = srcBounds->top(); - bounds[1] = srcBounds->bottom(); + bounds[0] = srcBounds.top(); + bounds[1] = srcBounds.bottom(); topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom()); bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom()); midRect.inset(0, radius); @@ -166,18 +167,20 @@ static void convolve_gaussian(GrRenderTargetContext* renderTargetContext, if (!bottomRect.isEmpty()) { renderTargetContext->clear(&bottomRect, 0, false); } + if (midRect.isEmpty()) { // Blur radius covers srcBounds; use bounds over entire draw convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset, - std::move(proxy), direction, radius, sigma, true, bounds); + std::move(proxy), direction, radius, sigma, mode, bounds); } else { // Draw right and left margins with bounds; middle without. convolve_gaussian_1d(renderTargetContext, clip, leftRect, srcOffset, - proxy, direction, radius, sigma, true, bounds); + proxy, direction, radius, sigma, mode, bounds); convolve_gaussian_1d(renderTargetContext, clip, rightRect, srcOffset, - proxy, direction, radius, sigma, true, bounds); + proxy, direction, radius, sigma, mode, bounds); convolve_gaussian_1d(renderTargetContext, clip, midRect, srcOffset, - std::move(proxy), direction, radius, sigma, false, bounds); + std::move(proxy), direction, radius, sigma, + GrTextureDomain::kIgnore_Mode, bounds); } } @@ -187,12 +190,12 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, sk_sp<GrTextureProxy> srcProxy, sk_sp<SkColorSpace> colorSpace, const SkIRect& dstBounds, - const SkIRect* srcBounds, + const SkIRect& srcBounds, float sigmaX, float sigmaY, + GrTextureDomain::Mode mode, SkBackingFit fit) { SkASSERT(context); - SkIRect clearRect; int scaleFactorX, radiusX; int scaleFactorY, radiusY; @@ -205,12 +208,11 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, SkIRect localDstBounds = SkIRect::MakeWH(dstBounds.width(), dstBounds.height()); SkIRect localSrcBounds; SkIRect srcRect; - if (srcBounds) { - srcRect = localSrcBounds = *srcBounds; - srcRect.offset(srcOffset); - srcBounds = &localSrcBounds; - } else { + if (GrTextureDomain::kIgnore_Mode == mode) { srcRect = localDstBounds; + } else { + srcRect = localSrcBounds = srcBounds; + srcRect.offset(srcOffset); } scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); @@ -241,11 +243,12 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, // We shouldn't be scaling because this is a small size blur SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY)); - convolve_gaussian_2d(dstRenderTargetContext.get(), clip, localDstBounds, srcOffset, - std::move(srcProxy), radiusX, radiusY, sigmaX, sigmaY, srcBounds); + convolve_gaussian_2d(dstRenderTargetContext.get(), + clip, localDstBounds, srcOffset, std::move(srcProxy), + radiusX, radiusY, sigmaX, sigmaY, srcBounds, mode); return dstRenderTargetContext; - } + } sk_sp<GrRenderTargetContext> tmpRenderTargetContext(context->makeDeferredRenderTargetContext( fit, width, height, config, colorSpace, 0, kBottomLeft_GrSurfaceOrigin)); @@ -257,12 +260,15 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY)); + // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerParams::FilterMode. + GrTextureDomain::Mode modeForScaling = + GrTextureDomain::kRepeat_Mode == mode ? GrTextureDomain::kDecal_Mode : mode; for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { GrPaint paint; paint.setGammaCorrect(dstRenderTargetContext->isGammaCorrect()); SkIRect dstRect(srcRect); - if (srcBounds && i == 1) { - SkRect domain = SkRect::Make(*srcBounds); + if (GrTextureDomain::kIgnore_Mode != mode && i == 1) { + SkRect domain = SkRect::Make(localSrcBounds); domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f, (i < scaleFactorY) ? SK_ScalarHalf : 0.0f); sk_sp<GrFragmentProcessor> fp(GrTextureDomainEffect::Make( @@ -270,7 +276,7 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, nullptr, SkMatrix::I(), domain, - GrTextureDomain::kDecal_Mode, + modeForScaling, GrSamplerParams::kBilerp_FilterMode)); paint.addColorFragmentProcessor(std::move(fp)); srcRect.offset(-srcOffset); @@ -311,7 +317,7 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect, std::move(srcProxy), Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, - srcBounds, srcOffset); + localSrcBounds, srcOffset, mode); srcRenderTargetContext = dstRenderTargetContext; srcProxy = srcRenderTargetContext->asTextureProxyRef(); if (!srcProxy) { @@ -320,6 +326,10 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, srcRect.offsetTo(0, 0); dstRenderTargetContext.swap(tmpRenderTargetContext); localSrcBounds = srcRect; + if (GrTextureDomain::kClamp_Mode == mode) { + // We need to adjust bounds because we only fill part of the srcRect in x-pass. + localSrcBounds.inset(0, radiusY); + } srcOffset.set(0, 0); } @@ -336,7 +346,7 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect, std::move(srcProxy), Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, - srcBounds, srcOffset); + localSrcBounds, srcOffset, mode); srcRenderTargetContext = dstRenderTargetContext; srcRect.offsetTo(0, 0); @@ -357,15 +367,26 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, GrPaint paint; paint.setGammaCorrect(dstRenderTargetContext->isGammaCorrect()); - // FIXME: this should be mitchell, not bilinear. - GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode); sk_sp<GrTextureProxy> proxy(srcRenderTargetContext->asTextureProxyRef()); if (!proxy) { return nullptr; } - paint.addColorTextureProcessor(std::move(proxy), - nullptr, SkMatrix::I(), params); + if (GrTextureDomain::kIgnore_Mode != mode) { + SkRect domain = SkRect::Make(localSrcBounds); + sk_sp<GrFragmentProcessor> fp(GrTextureDomainEffect::Make( + std::move(proxy), + nullptr, + SkMatrix::I(), + domain, + modeForScaling, + GrSamplerParams::kBilerp_FilterMode)); + paint.addColorFragmentProcessor(std::move(fp)); + } else { + // FIXME: this should be mitchell, not bilinear. + GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode); + paint.addColorTextureProcessor(std::move(proxy), nullptr, SkMatrix::I(), params); + } paint.setPorterDuffXPFactory(SkBlendMode::kSrc); SkIRect dstRect(srcRect); diff --git a/src/core/SkGpuBlurUtils.h b/src/core/SkGpuBlurUtils.h index c6c56b61f7..8a6f7b1922 100644 --- a/src/core/SkGpuBlurUtils.h +++ b/src/core/SkGpuBlurUtils.h @@ -10,6 +10,7 @@ #if SK_SUPPORT_GPU #include "GrRenderTargetContext.h" +#include "effects/GrTextureDomain.h" class GrContext; class GrTexture; @@ -30,17 +31,20 @@ namespace SkGpuBlurUtils { * no pixels will be sampled outside of this rectangle. * @param sigmaX The blur's standard deviation in X. * @param sigmaY The blur's standard deviation in Y. + * @param mode The mode to handle samples outside bounds. * @param fit backing fit for the returned render target context * @return The renderTargetContext containing the blurred result. */ - sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, - sk_sp<GrTextureProxy> src, - sk_sp<SkColorSpace> colorSpace, - const SkIRect& dstBounds, - const SkIRect* srcBounds, - float sigmaX, - float sigmaY, - SkBackingFit fit = SkBackingFit::kApprox); + sk_sp<GrRenderTargetContext> GaussianBlur( + GrContext* context, + sk_sp<GrTextureProxy> src, + sk_sp<SkColorSpace> colorSpace, + const SkIRect& dstBounds, + const SkIRect& srcBounds, + float sigmaX, + float sigmaY, + GrTextureDomain::Mode mode, + SkBackingFit fit = SkBackingFit::kApprox); }; #endif |