blob: e0f6523593d82a903d817e281e77614b870b8e75 [file] [log] [blame]
senorblanco@chromium.org60014ca2011-11-09 16:05:58 +00001/*
2 * Copyright 2011 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
robertphillips1579e3c2016-03-24 05:01:23 -07008#include "SkAutoPixmapStorage.h"
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +00009#include "SkColorPriv.h"
mtkleindce5ce42015-08-04 08:49:21 -070010#include "SkGpuBlurUtils.h"
11#include "SkOpts.h"
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +000012#include "SkReadBuffer.h"
robertphillips1579e3c2016-03-24 05:01:23 -070013#include "SkSpecialImage.h"
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +000014#include "SkWriteBuffer.h"
robertphillips1579e3c2016-03-24 05:01:23 -070015
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +000016#if SK_SUPPORT_GPU
senorblanco@chromium.org302cffb2012-08-01 20:16:34 +000017#include "GrContext.h"
robertphillipsd728f0c2016-11-21 11:05:03 -080018#include "GrTextureProxy.h"
robertphillips1de87df2016-01-14 06:03:29 -080019#include "SkGr.h"
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +000020#endif
senorblanco@chromium.org60014ca2011-11-09 16:05:58 +000021
vjiaoblacke1e5c742016-08-23 11:13:14 -070022class SkBlurImageFilterImpl : public SkImageFilter {
23public:
24 SkBlurImageFilterImpl(SkScalar sigmaX,
25 SkScalar sigmaY,
26 sk_sp<SkImageFilter> input,
27 const CropRect* cropRect);
28
29 SkRect computeFastBounds(const SkRect&) const override;
30
31 SK_TO_STRING_OVERRIDE()
32 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurImageFilterImpl)
33
vjiaoblacke1e5c742016-08-23 11:13:14 -070034protected:
35 void flatten(SkWriteBuffer&) const override;
36 sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
37 SkIPoint* offset) const override;
Matt Sarett6d72ed92017-04-10 16:35:33 -040038 sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
vjiaoblacke1e5c742016-08-23 11:13:14 -070039 SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override;
40
41private:
42 SkSize fSigma;
43 typedef SkImageFilter INHERITED;
44
45 friend class SkImageFilter;
46};
47
48SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter)
49 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl)
50SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
51
52///////////////////////////////////////////////////////////////////////////////
53
54sk_sp<SkImageFilter> SkImageFilter::MakeBlur(SkScalar sigmaX, SkScalar sigmaY,
robertphillips225db442016-04-17 14:27:05 -070055 sk_sp<SkImageFilter> input,
56 const CropRect* cropRect) {
57 if (0 == sigmaX && 0 == sigmaY && !cropRect) {
58 return input;
59 }
vjiaoblacke1e5c742016-08-23 11:13:14 -070060 return sk_sp<SkImageFilter>(new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect));
robertphillips225db442016-04-17 14:27:05 -070061}
62
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +000063// This rather arbitrary-looking value results in a maximum box blur kernel size
64// of 1000 pixels on the raster path, which matches the WebKit and Firefox
65// implementations. Since the GPU path does not compute a box blur, putting
66// the limit on sigma ensures consistent behaviour between the GPU and
67// raster paths.
68#define MAX_SIGMA SkIntToScalar(532)
69
senorblanco64049812016-02-01 10:32:42 -080070static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) {
senorblanco32673b92014-09-09 09:15:04 -070071 SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height());
72 ctm.mapVectors(&sigma, 1);
73 sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA);
74 sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA);
75 return sigma;
76}
77
vjiaoblacke1e5c742016-08-23 11:13:14 -070078SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX,
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000079 SkScalar sigmaY,
robertphillips6e7025a2016-04-04 04:31:25 -070080 sk_sp<SkImageFilter> input,
senorblanco24e06d52015-03-18 12:11:33 -070081 const CropRect* cropRect)
robertphillips6e7025a2016-04-04 04:31:25 -070082 : INHERITED(&input, 1, cropRect)
83 , fSigma(SkSize::Make(sigmaX, sigmaY)) {
senorblanco@chromium.org60014ca2011-11-09 16:05:58 +000084}
85
vjiaoblacke1e5c742016-08-23 11:13:14 -070086sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
reed9fa60da2014-08-21 07:59:51 -070087 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
88 SkScalar sigmaX = buffer.readScalar();
89 SkScalar sigmaY = buffer.readScalar();
vjiaoblacke1e5c742016-08-23 11:13:14 -070090 return SkImageFilter::MakeBlur(sigmaX, sigmaY, common.getInput(0), &common.cropRect());
reed9fa60da2014-08-21 07:59:51 -070091}
92
vjiaoblacke1e5c742016-08-23 11:13:14 -070093void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const {
senorblanco@chromium.org54e01b22011-11-16 18:20:47 +000094 this->INHERITED::flatten(buffer);
95 buffer.writeScalar(fSigma.fWidth);
96 buffer.writeScalar(fSigma.fHeight);
97}
98
robertphillips1579e3c2016-03-24 05:01:23 -070099static void get_box3_params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
100 int *highOffset) {
schenney@chromium.org73f3ded2011-12-20 22:31:40 +0000101 float pi = SkScalarToFloat(SK_ScalarPI);
102 int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000103 *kernelSize = d;
104 if (d % 2 == 1) {
105 *lowOffset = *highOffset = (d - 1) / 2;
106 *kernelSize3 = d;
107 } else {
108 *highOffset = d / 2;
109 *lowOffset = *highOffset - 1;
110 *kernelSize3 = d + 1;
111 }
112}
113
vjiaoblacke1e5c742016-08-23 11:13:14 -0700114sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* source,
robertphillipsd728f0c2016-11-21 11:05:03 -0800115 const Context& ctx,
116 SkIPoint* offset) const {
robertphillips1579e3c2016-03-24 05:01:23 -0700117 SkIPoint inputOffset = SkIPoint::Make(0, 0);
118
robertphillips2302de92016-03-24 07:26:32 -0700119 sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
robertphillips1579e3c2016-03-24 05:01:23 -0700120 if (!input) {
121 return nullptr;
senorblanco@chromium.org68400762013-05-24 15:04:07 +0000122 }
123
robertphillips1579e3c2016-03-24 05:01:23 -0700124 SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY,
125 input->width(), input->height());
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000126
senorblancoafec27f2016-02-16 09:11:18 -0800127 SkIRect dstBounds;
robertphillips1579e3c2016-03-24 05:01:23 -0700128 if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) {
129 return nullptr;
senorblancodb64af32015-12-09 10:11:43 -0800130 }
robertphillips1579e3c2016-03-24 05:01:23 -0700131 if (!inputBounds.intersect(dstBounds)) {
132 return nullptr;
reed@google.com76dd2772012-01-05 21:15:07 +0000133 }
134
robertphillips1579e3c2016-03-24 05:01:23 -0700135 const SkVector sigma = map_sigma(fSigma, ctx.ctm());
136
137#if SK_SUPPORT_GPU
robertphillips64612512016-04-08 12:10:42 -0700138 if (source->isTextureBacked()) {
139 GrContext* context = source->getContext();
Brian Osman5f500922016-12-29 11:50:48 -0500140
141 // Ensure the input is in the destination's gamut. This saves us from having to do the
142 // xform during the filter itself.
143 input = ImageToColorSpace(input.get(), ctx.outputProperties());
144
Robert Phillips8e1c4e62017-02-19 12:27:01 -0500145 sk_sp<GrTextureProxy> inputTexture(input->asTextureProxyRef(context));
Robert Phillips833dcf42016-11-18 08:44:13 -0500146 if (!inputTexture) {
147 return nullptr;
148 }
robertphillips64612512016-04-08 12:10:42 -0700149
robertphillips1579e3c2016-03-24 05:01:23 -0700150 if (0 == sigma.x() && 0 == sigma.y()) {
151 offset->fX = inputBounds.x();
152 offset->fY = inputBounds.y();
153 return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(),
robertphillips2302de92016-03-24 07:26:32 -0700154 -inputOffset.y()));
robertphillips1579e3c2016-03-24 05:01:23 -0700155 }
156
robertphillips1579e3c2016-03-24 05:01:23 -0700157 offset->fX = dstBounds.fLeft;
158 offset->fY = dstBounds.fTop;
159 inputBounds.offset(-inputOffset);
160 dstBounds.offset(-inputOffset);
Brian Osman5f500922016-12-29 11:50:48 -0500161 // Typically, we would create the RTC with the output's color space (from ctx), but we
162 // always blur in the PixelConfig of the *input*. Those might not be compatible (if they
163 // have different transfer functions). We've already guaranteed that those color spaces
164 // have the same gamut, so in this case, we do everything in the input's color space.
Brian Osman11052242016-10-27 14:47:55 -0400165 sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
brianosmandfe4f2e2016-07-21 13:28:36 -0700166 context,
Robert Phillips08c5ec72017-01-30 12:26:47 -0500167 std::move(inputTexture),
Brian Osman5f500922016-12-29 11:50:48 -0500168 sk_ref_sp(input->getColorSpace()),
brianosmandfe4f2e2016-07-21 13:28:36 -0700169 dstBounds,
170 &inputBounds,
171 sigma.x(),
172 sigma.y()));
Brian Osman11052242016-10-27 14:47:55 -0400173 if (!renderTargetContext) {
robertphillips1579e3c2016-03-24 05:01:23 -0700174 return nullptr;
175 }
176
Robert Phillips2c6d2bf2017-02-21 10:19:29 -0500177 return SkSpecialImage::MakeDeferredFromGpu(context,
178 SkIRect::MakeWH(dstBounds.width(),
179 dstBounds.height()),
180 kNeedNewImageUniqueID_SpecialImage,
181 renderTargetContext->asTextureProxyRef(),
182 renderTargetContext->refColorSpace(),
183 &source->props());
robertphillips1579e3c2016-03-24 05:01:23 -0700184 }
185#endif
senorblanco@chromium.org2bfe36b2014-01-20 19:58:28 +0000186
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000187 int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
188 int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
robertphillips1579e3c2016-03-24 05:01:23 -0700189 get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
190 get_box3_params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000191
192 if (kernelSizeX < 0 || kernelSizeY < 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700193 return nullptr;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000194 }
195
196 if (kernelSizeX == 0 && kernelSizeY == 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700197 offset->fX = inputBounds.x();
198 offset->fY = inputBounds.y();
199 return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(),
robertphillips2302de92016-03-24 07:26:32 -0700200 -inputOffset.y()));
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000201 }
202
robertphillips64612512016-04-08 12:10:42 -0700203 SkBitmap inputBM;
robertphillips1579e3c2016-03-24 05:01:23 -0700204
robertphillips64612512016-04-08 12:10:42 -0700205 if (!input->getROPixels(&inputBM)) {
robertphillips1579e3c2016-03-24 05:01:23 -0700206 return nullptr;
senorblanco64049812016-02-01 10:32:42 -0800207 }
208
robertphillips64612512016-04-08 12:10:42 -0700209 if (inputBM.colorType() != kN32_SkColorType) {
robertphillips1579e3c2016-03-24 05:01:23 -0700210 return nullptr;
senorblanco64049812016-02-01 10:32:42 -0800211 }
senorblanco64049812016-02-01 10:32:42 -0800212
robertphillips1579e3c2016-03-24 05:01:23 -0700213 SkImageInfo info = SkImageInfo::Make(dstBounds.width(), dstBounds.height(),
robertphillips64612512016-04-08 12:10:42 -0700214 inputBM.colorType(), inputBM.alphaType());
robertphillips1579e3c2016-03-24 05:01:23 -0700215
216 SkBitmap tmp, dst;
217 if (!tmp.tryAllocPixels(info) || !dst.tryAllocPixels(info)) {
218 return nullptr;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000219 }
robertphillips1579e3c2016-03-24 05:01:23 -0700220
robertphillips64612512016-04-08 12:10:42 -0700221 SkAutoLockPixels inputLock(inputBM), tmpLock(tmp), dstLock(dst);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000222
senorblanco29a440d2015-11-02 12:55:47 -0800223 offset->fX = dstBounds.fLeft;
224 offset->fY = dstBounds.fTop;
robertphillips1579e3c2016-03-24 05:01:23 -0700225 SkPMColor* t = tmp.getAddr32(0, 0);
226 SkPMColor* d = dst.getAddr32(0, 0);
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000227 int w = dstBounds.width(), h = dstBounds.height();
robertphillips64612512016-04-08 12:10:42 -0700228 const SkPMColor* s = inputBM.getAddr32(inputBounds.x() - inputOffset.x(),
229 inputBounds.y() - inputOffset.y());
robertphillips1579e3c2016-03-24 05:01:23 -0700230 inputBounds.offset(-dstBounds.x(), -dstBounds.y());
senorblanco29a440d2015-11-02 12:55:47 -0800231 dstBounds.offset(-dstBounds.x(), -dstBounds.y());
robertphillips1579e3c2016-03-24 05:01:23 -0700232 SkIRect inputBoundsT = SkIRect::MakeLTRB(inputBounds.top(), inputBounds.left(),
233 inputBounds.bottom(), inputBounds.right());
senorblanco29a440d2015-11-02 12:55:47 -0800234 SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width());
robertphillips64612512016-04-08 12:10:42 -0700235 int sw = int(inputBM.rowBytes() >> 2);
senorblanco@chromium.org27eec462013-11-08 20:49:04 +0000236
mtkleindce5ce42015-08-04 08:49:21 -0700237 /**
238 *
239 * In order to make memory accesses cache-friendly, we reorder the passes to
240 * use contiguous memory reads wherever possible.
241 *
242 * For example, the 6 passes of the X-and-Y blur case are rewritten as
243 * follows. Instead of 3 passes in X and 3 passes in Y, we perform
244 * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X,
245 * then 1 pass in X transposed to Y on write.
246 *
247 * +----+ +----+ +----+ +---+ +---+ +---+ +----+
248 * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB |
249 * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+
250 * +---+ +---+ +---+
251 *
252 * In this way, two of the y-blurs become x-blurs applied to transposed
253 * images, and all memory reads are contiguous.
254 */
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000255 if (kernelSizeX > 0 && kernelSizeY > 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700256 SkOpts::box_blur_xx(s, sw, inputBounds, t, kernelSizeX, lowOffsetX, highOffsetX, w, h);
257 SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX, highOffsetX, lowOffsetX, w, h);
258 SkOpts::box_blur_xy(d, w, dstBounds, t, kernelSizeX3, highOffsetX, highOffsetX, w, h);
259 SkOpts::box_blur_xx(t, h, dstBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
260 SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
261 SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000262 } else if (kernelSizeX > 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700263 SkOpts::box_blur_xx(s, sw, inputBounds, d, kernelSizeX, lowOffsetX, highOffsetX, w, h);
264 SkOpts::box_blur_xx(d, w, dstBounds, t, kernelSizeX, highOffsetX, lowOffsetX, w, h);
265 SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX3, highOffsetX, highOffsetX, w, h);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000266 } else if (kernelSizeY > 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700267 SkOpts::box_blur_yx(s, sw, inputBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
268 SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
269 SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000270 }
robertphillips1579e3c2016-03-24 05:01:23 -0700271
robertphillips3e302272016-04-20 11:48:36 -0700272 return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(),
robertphillips1579e3c2016-03-24 05:01:23 -0700273 dstBounds.height()),
brianosman898235c2016-04-06 07:38:23 -0700274 dst, &source->props());
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000275}
276
Matt Sarett6d72ed92017-04-10 16:35:33 -0400277sk_sp<SkImageFilter> SkBlurImageFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer)
278const {
279 SkASSERT(1 == this->countInputs());
280 if (!this->getInput(0)) {
281 return sk_ref_sp(const_cast<SkBlurImageFilterImpl*>(this));
282 }
283
284 sk_sp<SkImageFilter> input = this->getInput(0)->makeColorSpace(xformer);
285 return SkImageFilter::MakeBlur(fSigma.width(), fSigma.height(), std::move(input),
286 this->getCropRectIfSet());
287}
senorblanco@chromium.org336d1d72014-01-27 21:03:17 +0000288
vjiaoblacke1e5c742016-08-23 11:13:14 -0700289SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const {
senorblancoe5e79842016-03-21 14:51:59 -0700290 SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
Mike Reeda99b6ce2017-02-04 11:04:26 -0500291 bounds.outset(fSigma.width() * 3, fSigma.height() * 3);
senorblancoe5e79842016-03-21 14:51:59 -0700292 return bounds;
senorblanco@chromium.org336d1d72014-01-27 21:03:17 +0000293}
senorblanco@chromium.orgc4b12f12014-02-05 17:51:22 +0000294
vjiaoblacke1e5c742016-08-23 11:13:14 -0700295SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
senorblancoe5e79842016-03-21 14:51:59 -0700296 MapDirection) const {
senorblanco64049812016-02-01 10:32:42 -0800297 SkVector sigma = map_sigma(fSigma, ctm);
Mike Reeda99b6ce2017-02-04 11:04:26 -0500298 return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3));
senorblanco@chromium.orgc4b12f12014-02-05 17:51:22 +0000299}
300
robertphillipsf3f5bad2014-12-19 13:49:15 -0800301#ifndef SK_IGNORE_TO_STRING
vjiaoblacke1e5c742016-08-23 11:13:14 -0700302void SkBlurImageFilterImpl::toString(SkString* str) const {
303 str->appendf("SkBlurImageFilterImpl: (");
robertphillips33cca882014-12-22 06:52:04 -0800304 str->appendf("sigma: (%f, %f) input (", fSigma.fWidth, fSigma.fHeight);
305
306 if (this->getInput(0)) {
307 this->getInput(0)->toString(str);
308 }
309
310 str->append("))");
robertphillipsf3f5bad2014-12-19 13:49:15 -0800311}
312#endif