aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar wutao <wutao@google.com>2017-06-30 10:44:45 -0700
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-06-30 18:11:16 +0000
commit039a7c70cea29aa40c4fb880b0d6bb523d449568 (patch)
treeb62a5274d4b1cbcaac89ad5fb8bf86118c7caa87
parent981deec8b2928f07e3899849bed31bfbb8c19b9b (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>
-rw-r--r--gm/imageblurclampmode.cpp12
-rw-r--r--gm/imageblurrepeatmode.cpp125
-rw-r--r--gn/gm.gni1
-rw-r--r--include/effects/SkBlurImageFilter.h13
-rw-r--r--src/core/SkBlurImageFilter.cpp45
-rw-r--r--src/core/SkGpuBlurUtils.cpp99
-rw-r--r--src/core/SkGpuBlurUtils.h20
-rw-r--r--src/effects/SkBlurMaskFilter.cpp35
-rw-r--r--src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp59
-rw-r--r--src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h15
10 files changed, 327 insertions, 97 deletions
diff --git a/gm/imageblurclampmode.cpp b/gm/imageblurclampmode.cpp
index 0f97ef386e..f0cad82ce7 100644
--- a/gm/imageblurclampmode.cpp
+++ b/gm/imageblurclampmode.cpp
@@ -65,6 +65,8 @@ protected:
return SkISize::Make(850, 920);
}
+ bool runAsBench() const override { return true; }
+
void onDraw(SkCanvas* canvas) override {
sk_sp<SkImage> image(make_image(canvas));
@@ -73,15 +75,19 @@ protected:
// blur.
for (auto sigma: { 0.6f, 3.0f, 8.0f, 20.0f }) {
canvas->save();
- sk_sp<SkImageFilter> filter(SkBlurImageFilter::Make(sigma, 0.0f, nullptr));
+ sk_sp<SkImageFilter> filter(
+ SkBlurImageFilter::Make(sigma, 0.0f, nullptr, nullptr,
+ SkBlurImageFilter::kClamp_TileMode));
draw_image(canvas, image, std::move(filter));
canvas->translate(image->width() + 20, 0);
- filter = SkBlurImageFilter::Make(0.0f, sigma, nullptr);
+ filter = SkBlurImageFilter::Make(0.0f, sigma, nullptr, nullptr,
+ SkBlurImageFilter::kClamp_TileMode);
draw_image(canvas, image, std::move(filter));
canvas->translate(image->width() + 20, 0);
- filter = SkBlurImageFilter::Make(sigma, sigma, nullptr);
+ filter = SkBlurImageFilter::Make(sigma, sigma, nullptr, nullptr,
+ SkBlurImageFilter::kClamp_TileMode);
draw_image(canvas, image, std::move(filter));
canvas->translate(image->width() + 20, 0);
diff --git a/gm/imageblurrepeatmode.cpp b/gm/imageblurrepeatmode.cpp
new file mode 100644
index 0000000000..6f38029e71
--- /dev/null
+++ b/gm/imageblurrepeatmode.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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.h"
+#include "sk_tool_utils.h"
+#include "SkSurface.h"
+#include "SkBlurImageFilter.h"
+
+static sk_sp<SkSurface> make_surface(SkCanvas* canvas, const SkImageInfo& info) {
+ auto surface = canvas->makeSurface(info);
+ if (!surface) {
+ surface = SkSurface::MakeRaster(info);
+ }
+ return surface;
+}
+
+static sk_sp<SkImage> make_image(SkCanvas* canvas, int direction) {
+ SkImageInfo info = SkImageInfo::MakeN32Premul(250, 200);
+ auto surface = make_surface(canvas, info);
+ SkCanvas* c = surface->getCanvas();
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ const SkColor colors[] = {
+ SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN, SK_ColorYELLOW, SK_ColorBLACK
+ };
+
+ int width = 25;
+ bool xDirection = (direction & 0x1) == 1;
+ bool yDirection = (direction & 0x2) == 2;
+ if (xDirection) {
+ for (int x = 0; x < info.width(); x += width) {
+ paint.setColor(colors[x/width % 5]);
+ if (yDirection) {
+ paint.setAlpha(127);
+ }
+ c->drawRect(SkRect::MakeXYWH(x, 0, width, info.height()), paint);
+ }
+ }
+
+ if (yDirection) {
+ for (int y = 0; y < info.height(); y += width) {
+ paint.setColor(colors[y/width % 5]);
+ if (xDirection) {
+ paint.setAlpha(127);
+ }
+ c->drawRect(SkRect::MakeXYWH(0, y, info.width(), width), paint);
+ }
+ }
+ return surface->makeImageSnapshot();
+}
+
+static void draw_image(SkCanvas* canvas, const sk_sp<SkImage> image, sk_sp<SkImageFilter> filter) {
+ SkAutoCanvasRestore acr(canvas, true);
+ SkPaint paint;
+ paint.setImageFilter(std::move(filter));
+
+ canvas->translate(SkIntToScalar(30), 0);
+ canvas->clipRect(SkRect::MakeIWH(image->width(),image->height()));
+ canvas->drawImage(image, 0, 0, &paint);
+}
+
+namespace skiagm {
+
+// This GM draws a colorful grids with different blur settings.
+class ImageBlurRepeatModeGM : public GM {
+public:
+ ImageBlurRepeatModeGM() {
+ this->setBGColor(sk_tool_utils::color_to_565(0xFFCCCCCC));
+ }
+
+protected:
+
+ SkString onShortName() override {
+ return SkString("imageblurrepeatmode");
+ }
+
+ SkISize onISize() override {
+ return SkISize::Make(850, 920);
+ }
+
+ bool runAsBench() const override { return true; }
+
+ void onDraw(SkCanvas* canvas) override {
+ sk_sp<SkImage> image[] =
+ { make_image(canvas, 1), make_image(canvas, 2), make_image(canvas, 3) };
+
+ canvas->translate(0, 30);
+ // Test different kernel size, including the one to launch 2d Gaussian
+ // blur.
+ for (auto sigma: { 0.6f, 3.0f, 8.0f, 20.0f }) {
+ canvas->save();
+ sk_sp<SkImageFilter> filter(
+ SkBlurImageFilter::Make(sigma, 0.0f, nullptr, nullptr,
+ SkBlurImageFilter::kRepeat_TileMode));
+ draw_image(canvas, image[0], std::move(filter));
+ canvas->translate(image[0]->width() + 20, 0);
+
+ filter = SkBlurImageFilter::Make(0.0f, sigma, nullptr, nullptr,
+ SkBlurImageFilter::kRepeat_TileMode);
+ draw_image(canvas, image[1], std::move(filter));
+ canvas->translate(image[1]->width() + 20, 0);
+
+ filter = SkBlurImageFilter::Make(sigma, sigma, nullptr, nullptr,
+ SkBlurImageFilter::kRepeat_TileMode);
+ draw_image(canvas, image[2], std::move(filter));
+ canvas->translate(image[2]->width() + 20, 0);
+
+ canvas->restore();
+ canvas->translate(0, image[0]->height() + 20);
+ }
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new ImageBlurRepeatModeGM;)
+}
diff --git a/gn/gm.gni b/gn/gm.gni
index f57c232f03..b9bfd4bb35 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -161,6 +161,7 @@ gm_sources = [
"$_gm/imageblur.cpp",
"$_gm/imageblur2.cpp",
"$_gm/imageblurclampmode.cpp",
+ "$_gm/imageblurrepeatmode.cpp",
"$_gm/imageblurtiled.cpp",
"$_gm/imagefilters.cpp",
"$_gm/imagefiltersbase.cpp",
diff --git a/include/effects/SkBlurImageFilter.h b/include/effects/SkBlurImageFilter.h
index 0cb864c145..bae835ccf5 100644
--- a/include/effects/SkBlurImageFilter.h
+++ b/include/effects/SkBlurImageFilter.h
@@ -12,9 +12,20 @@
class SK_API SkBlurImageFilter {
public:
+ /*! \enum TileMode */
+ enum TileMode {
+ kIgnore_TileMode = 0, /*!< Ignore the image's edge pixels. */
+ kClamp_TileMode, /*!< Clamp to the image's edge pixels. */
+ /*!< This re-weights the filter so samples outside have no effect */
+ kRepeat_TileMode, /*!< Wrap around to the image's opposite edge. */
+ kClampToBlack_TileMode, /*!< Fill with transparent black. */
+ kMax_TileMode = kClampToBlack_TileMode
+ };
+
static sk_sp<SkImageFilter> Make(SkScalar sigmaX, SkScalar sigmaY,
sk_sp<SkImageFilter> input,
- const SkImageFilter::CropRect* cropRect = nullptr);
+ const SkImageFilter::CropRect* cropRect = nullptr,
+ TileMode tileMode = TileMode::kClampToBlack_TileMode);
};
#endif
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
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index df60b53288..be11cf4443 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -27,6 +27,7 @@
#include "GrStyle.h"
#include "GrTextureProxy.h"
#include "effects/GrSimpleTextureEffect.h"
+#include "effects/GrTextureDomain.h"
#include "glsl/GrGLSLFragmentProcessor.h"
#include "glsl/GrGLSLFragmentShaderBuilder.h"
#include "glsl/GrGLSLProgramDataManager.h"
@@ -1135,15 +1136,16 @@ static sk_sp<GrTextureProxy> find_or_create_rrect_blur_mask(GrContext* context,
if (!srcProxy) {
return nullptr;
}
- sk_sp<GrRenderTargetContext> rtc2(SkGpuBlurUtils::GaussianBlur(context,
- std::move(srcProxy),
- nullptr,
- SkIRect::MakeWH(
- size.fWidth,
- size.fHeight),
- nullptr,
- xformedSigma, xformedSigma,
- SkBackingFit::kExact));
+ sk_sp<GrRenderTargetContext> rtc2(
+ SkGpuBlurUtils::GaussianBlur(context,
+ std::move(srcProxy),
+ nullptr,
+ SkIRect::MakeWH(size.fWidth, size.fHeight),
+ SkIRect::EmptyIRect(),
+ xformedSigma,
+ xformedSigma,
+ GrTextureDomain::kIgnore_Mode,
+ SkBackingFit::kExact));
if (!rtc2) {
return nullptr;
}
@@ -1495,12 +1497,15 @@ sk_sp<GrTextureProxy> SkBlurMaskFilterImpl::filterMaskGPU(GrContext* context,
// If we're doing a normal blur, we can clobber the pathTexture in the
// gaussianBlur. Otherwise, we need to save it for later compositing.
bool isNormalBlur = (kNormal_SkBlurStyle == fBlurStyle);
- sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(context,
- srcProxy,
- nullptr, clipRect,
- nullptr,
- xformedSigma,
- xformedSigma));
+ sk_sp<GrRenderTargetContext> renderTargetContext(
+ SkGpuBlurUtils::GaussianBlur(context,
+ srcProxy,
+ nullptr,
+ clipRect,
+ SkIRect::EmptyIRect(),
+ xformedSigma,
+ xformedSigma,
+ GrTextureDomain::kIgnore_Mode));
if (!renderTargetContext) {
return nullptr;
}
diff --git a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
index b089409afa..4d1a99d83e 100644
--- a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
+++ b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
@@ -65,6 +65,7 @@ void GrGLConvolutionEffect::emitCode(EmitArgs& args) {
const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni);
fragBuilder->codeAppendf("vec2 coord = %s - %d.0 * %s;", coords2D.c_str(), ce.radius(), imgInc);
+ fragBuilder->codeAppend("vec2 coordSampled = vec2(0, 0);");
// Manually unroll loop because some drivers don't; yields 20-30% speedup.
const char* kVecSuffix[4] = {".x", ".y", ".z", ".w"};
@@ -75,19 +76,40 @@ void GrGLConvolutionEffect::emitCode(EmitArgs& args) {
kernel.appendArrayAccess(index.c_str(), &kernelIndex);
kernelIndex.append(kVecSuffix[i & 0x3]);
+ fragBuilder->codeAppend("coordSampled = coord;");
if (ce.useBounds()) {
// We used to compute a bool indicating whether we're in bounds or not, cast it to a
// float, and then mul weight*texture_sample by the float. However, the Adreno 430 seems
// to have a bug that caused corruption.
const char* bounds = uniformHandler->getUniformCStr(fBoundsUni);
const char* component = ce.direction() == Gr1DKernelEffect::kY_Direction ? "y" : "x";
- fragBuilder->codeAppendf("if (coord.%s >= %s.x && coord.%s <= %s.y) {", component,
- bounds, component, bounds);
+
+ switch (ce.mode()) {
+ case GrTextureDomain::kClamp_Mode: {
+ fragBuilder->codeAppendf("coordSampled.%s = clamp(coord.%s, %s.x, %s.y);\n",
+ component, component, bounds, bounds);
+ break;
+ }
+ case GrTextureDomain::kRepeat_Mode: {
+ fragBuilder->codeAppendf("coordSampled.%s = "
+ "mod(coord.%s - %s.x, %s.y - %s.x) + %s.x;\n",
+ component, component, bounds, bounds, bounds, bounds);
+ break;
+ }
+ case GrTextureDomain::kDecal_Mode: {
+ fragBuilder->codeAppendf("if (coord.%s >= %s.x && coord.%s <= %s.y) {",
+ component, bounds, component, bounds);
+ break;
+ }
+ default: {
+ SkFAIL("Unsupported operation.");
+ }
+ }
}
fragBuilder->codeAppendf("%s += ", args.fOutputColor);
- fragBuilder->appendTextureLookup(args.fTexSamplers[0], "coord");
+ fragBuilder->appendTextureLookup(args.fTexSamplers[0], "coordSampled");
fragBuilder->codeAppendf(" * %s;\n", kernelIndex.c_str());
- if (ce.useBounds()) {
+ if (GrTextureDomain::kDecal_Mode == ce.mode()) {
fragBuilder->codeAppend("}");
}
fragBuilder->codeAppendf("coord += %s;\n", imgInc);
@@ -115,7 +137,13 @@ void GrGLConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdman,
}
pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
if (conv.useBounds()) {
- const int* bounds = conv.bounds();
+ float bounds[2] = {0};
+ bounds[0] = conv.bounds()[0];
+ bounds[1] = conv.bounds()[1];
+ if (GrTextureDomain::kClamp_Mode == conv.mode()) {
+ bounds[0] += SK_ScalarHalf;
+ bounds[1] -= SK_ScalarHalf;
+ }
if (Gr1DKernelEffect::kX_Direction == conv.direction()) {
SkScalar inv = SkScalarInvert(SkIntToScalar(texture.width()));
pdman.set2f(fBoundsUni, inv * bounds[0], inv * bounds[1]);
@@ -140,11 +168,9 @@ void GrGLConvolutionEffect::GenKey(const GrProcessor& processor, const GrShaderC
const GrGaussianConvolutionFragmentProcessor& conv =
processor.cast<GrGaussianConvolutionFragmentProcessor>();
uint32_t key = conv.radius();
- key <<= 2;
- if (conv.useBounds()) {
- key |= 0x2;
- key |= GrGaussianConvolutionFragmentProcessor::kY_Direction == conv.direction() ? 0x1 : 0x0;
- }
+ key <<= 3;
+ key |= GrGaussianConvolutionFragmentProcessor::kY_Direction == conv.direction() ? 0x4 : 0x0;
+ key |= static_cast<uint32_t>(conv.mode());
b->add32(key);
}
@@ -172,13 +198,13 @@ GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
Direction direction,
int radius,
float gaussianSigma,
- bool useBounds,
+ GrTextureDomain::Mode mode,
int bounds[2])
: INHERITED{ModulationFlags(proxy->config()),
GR_PROXY_MOVE(proxy),
direction,
radius}
- , fUseBounds(useBounds) {
+ , fMode(mode) {
this->initClassID<GrGaussianConvolutionFragmentProcessor>();
SkASSERT(radius <= kMaxKernelRadius);
@@ -202,7 +228,7 @@ bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor
const GrGaussianConvolutionFragmentProcessor& s =
sBase.cast<GrGaussianConvolutionFragmentProcessor>();
return (this->radius() == s.radius() && this->direction() == s.direction() &&
- this->useBounds() == s.useBounds() &&
+ this->mode() == s.mode() &&
0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) &&
0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
}
@@ -218,8 +244,8 @@ sk_sp<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate(
: GrProcessorUnitTest::kAlphaTextureIdx;
sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
- bool useBounds = d->fRandom->nextBool();
int bounds[2];
+ int modeIdx = d->fRandom->nextRangeU(0, GrTextureDomain::kModeCount-1);
Direction dir;
if (d->fRandom->nextBool()) {
@@ -235,7 +261,8 @@ sk_sp<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate(
int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius);
float sigma = radius / 3.f;
- return GrGaussianConvolutionFragmentProcessor::Make(d->textureProxy(texIdx),
- dir, radius, sigma, useBounds, bounds);
+ return GrGaussianConvolutionFragmentProcessor::Make(
+ d->textureProxy(texIdx),
+ dir, radius, sigma, static_cast<GrTextureDomain::Mode>(modeIdx), bounds);
}
#endif
diff --git a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h
index d6398726b5..5f520df23b 100644
--- a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h
+++ b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h
@@ -9,6 +9,7 @@
#define GrGaussianConvolutionFragmentProcessor_DEFINED
#include "Gr1DKernelEffect.h"
+#include "GrTextureDomain.h"
/**
* A 1D Gaussian convolution effect. The kernel is computed as an array of 2 * half-width weights.
@@ -22,10 +23,10 @@ public:
Direction dir,
int halfWidth,
float gaussianSigma,
- bool useBounds,
+ GrTextureDomain::Mode mode,
int* bounds) {
return sk_sp<GrFragmentProcessor>(new GrGaussianConvolutionFragmentProcessor(
- std::move(proxy), dir, halfWidth, gaussianSigma, useBounds, bounds));
+ std::move(proxy), dir, halfWidth, gaussianSigma, mode, bounds));
}
~GrGaussianConvolutionFragmentProcessor() override;
@@ -33,7 +34,9 @@ public:
const float* kernel() const { return fKernel; }
const int* bounds() const { return fBounds; }
- bool useBounds() const { return fUseBounds; }
+ bool useBounds() const { return fMode != GrTextureDomain::kIgnore_Mode; }
+
+ GrTextureDomain::Mode mode() const { return fMode; }
const char* name() const override { return "GaussianConvolution"; }
@@ -49,8 +52,8 @@ public:
private:
/// Convolve with a Gaussian kernel
GrGaussianConvolutionFragmentProcessor(sk_sp<GrTextureProxy>, Direction,
- int halfWidth, float gaussianSigma, bool useBounds,
- int bounds[2]);
+ int halfWidth, float gaussianSigma,
+ GrTextureDomain::Mode mode, int bounds[2]);
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
@@ -63,8 +66,8 @@ private:
// TODO: Inline the kernel constants into the generated shader code. This may involve pulling
// some of the logic from SkGpuBlurUtils into this class related to radius/sigma calculations.
float fKernel[kMaxKernelWidth];
- bool fUseBounds;
int fBounds[2];
+ GrTextureDomain::Mode fMode;
typedef Gr1DKernelEffect INHERITED;
};