blob: 92a5d0fe0ba59a4ef6e0ad50c224190cd64c6f46 [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;
38 SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override;
39
40private:
41 SkSize fSigma;
42 typedef SkImageFilter INHERITED;
43
44 friend class SkImageFilter;
45};
46
47SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter)
48 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl)
49SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
50
51///////////////////////////////////////////////////////////////////////////////
52
53sk_sp<SkImageFilter> SkImageFilter::MakeBlur(SkScalar sigmaX, SkScalar sigmaY,
robertphillips225db442016-04-17 14:27:05 -070054 sk_sp<SkImageFilter> input,
55 const CropRect* cropRect) {
56 if (0 == sigmaX && 0 == sigmaY && !cropRect) {
57 return input;
58 }
vjiaoblacke1e5c742016-08-23 11:13:14 -070059 return sk_sp<SkImageFilter>(new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect));
robertphillips225db442016-04-17 14:27:05 -070060}
61
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +000062// This rather arbitrary-looking value results in a maximum box blur kernel size
63// of 1000 pixels on the raster path, which matches the WebKit and Firefox
64// implementations. Since the GPU path does not compute a box blur, putting
65// the limit on sigma ensures consistent behaviour between the GPU and
66// raster paths.
67#define MAX_SIGMA SkIntToScalar(532)
68
senorblanco64049812016-02-01 10:32:42 -080069static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) {
senorblanco32673b92014-09-09 09:15:04 -070070 SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height());
71 ctm.mapVectors(&sigma, 1);
72 sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA);
73 sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA);
74 return sigma;
75}
76
vjiaoblacke1e5c742016-08-23 11:13:14 -070077SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX,
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000078 SkScalar sigmaY,
robertphillips6e7025a2016-04-04 04:31:25 -070079 sk_sp<SkImageFilter> input,
senorblanco24e06d52015-03-18 12:11:33 -070080 const CropRect* cropRect)
robertphillips6e7025a2016-04-04 04:31:25 -070081 : INHERITED(&input, 1, cropRect)
82 , fSigma(SkSize::Make(sigmaX, sigmaY)) {
senorblanco@chromium.org60014ca2011-11-09 16:05:58 +000083}
84
vjiaoblacke1e5c742016-08-23 11:13:14 -070085sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
reed9fa60da2014-08-21 07:59:51 -070086 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
87 SkScalar sigmaX = buffer.readScalar();
88 SkScalar sigmaY = buffer.readScalar();
vjiaoblacke1e5c742016-08-23 11:13:14 -070089 return SkImageFilter::MakeBlur(sigmaX, sigmaY, common.getInput(0), &common.cropRect());
reed9fa60da2014-08-21 07:59:51 -070090}
91
vjiaoblacke1e5c742016-08-23 11:13:14 -070092void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const {
senorblanco@chromium.org54e01b22011-11-16 18:20:47 +000093 this->INHERITED::flatten(buffer);
94 buffer.writeScalar(fSigma.fWidth);
95 buffer.writeScalar(fSigma.fHeight);
96}
97
robertphillips1579e3c2016-03-24 05:01:23 -070098static void get_box3_params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
99 int *highOffset) {
schenney@chromium.org73f3ded2011-12-20 22:31:40 +0000100 float pi = SkScalarToFloat(SK_ScalarPI);
101 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 +0000102 *kernelSize = d;
103 if (d % 2 == 1) {
104 *lowOffset = *highOffset = (d - 1) / 2;
105 *kernelSize3 = d;
106 } else {
107 *highOffset = d / 2;
108 *lowOffset = *highOffset - 1;
109 *kernelSize3 = d + 1;
110 }
111}
112
vjiaoblacke1e5c742016-08-23 11:13:14 -0700113sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* source,
robertphillipsd728f0c2016-11-21 11:05:03 -0800114 const Context& ctx,
115 SkIPoint* offset) const {
robertphillips1579e3c2016-03-24 05:01:23 -0700116 SkIPoint inputOffset = SkIPoint::Make(0, 0);
117
robertphillips2302de92016-03-24 07:26:32 -0700118 sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
robertphillips1579e3c2016-03-24 05:01:23 -0700119 if (!input) {
120 return nullptr;
senorblanco@chromium.org68400762013-05-24 15:04:07 +0000121 }
122
robertphillips1579e3c2016-03-24 05:01:23 -0700123 SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY,
124 input->width(), input->height());
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000125
senorblancoafec27f2016-02-16 09:11:18 -0800126 SkIRect dstBounds;
robertphillips1579e3c2016-03-24 05:01:23 -0700127 if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) {
128 return nullptr;
senorblancodb64af32015-12-09 10:11:43 -0800129 }
robertphillips1579e3c2016-03-24 05:01:23 -0700130 if (!inputBounds.intersect(dstBounds)) {
131 return nullptr;
reed@google.com76dd2772012-01-05 21:15:07 +0000132 }
133
robertphillips1579e3c2016-03-24 05:01:23 -0700134 const SkVector sigma = map_sigma(fSigma, ctx.ctm());
135
136#if SK_SUPPORT_GPU
robertphillips64612512016-04-08 12:10:42 -0700137 if (source->isTextureBacked()) {
138 GrContext* context = source->getContext();
Brian Osman5f500922016-12-29 11:50:48 -0500139
140 // Ensure the input is in the destination's gamut. This saves us from having to do the
141 // xform during the filter itself.
142 input = ImageToColorSpace(input.get(), ctx.outputProperties());
143
robertphillipsc91fd342016-04-25 12:32:54 -0700144 sk_sp<GrTexture> inputTexture(input->asTextureRef(context));
Robert Phillips833dcf42016-11-18 08:44:13 -0500145 if (!inputTexture) {
146 return nullptr;
147 }
robertphillips64612512016-04-08 12:10:42 -0700148
robertphillips1579e3c2016-03-24 05:01:23 -0700149 if (0 == sigma.x() && 0 == sigma.y()) {
150 offset->fX = inputBounds.x();
151 offset->fY = inputBounds.y();
152 return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(),
robertphillips2302de92016-03-24 07:26:32 -0700153 -inputOffset.y()));
robertphillips1579e3c2016-03-24 05:01:23 -0700154 }
155
robertphillips1579e3c2016-03-24 05:01:23 -0700156 offset->fX = dstBounds.fLeft;
157 offset->fY = dstBounds.fTop;
158 inputBounds.offset(-inputOffset);
159 dstBounds.offset(-inputOffset);
Brian Osman5f500922016-12-29 11:50:48 -0500160 // Typically, we would create the RTC with the output's color space (from ctx), but we
161 // always blur in the PixelConfig of the *input*. Those might not be compatible (if they
162 // have different transfer functions). We've already guaranteed that those color spaces
163 // have the same gamut, so in this case, we do everything in the input's color space.
Brian Osman11052242016-10-27 14:47:55 -0400164 sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
brianosmandfe4f2e2016-07-21 13:28:36 -0700165 context,
166 inputTexture.get(),
Brian Osman5f500922016-12-29 11:50:48 -0500167 sk_ref_sp(input->getColorSpace()),
brianosmandfe4f2e2016-07-21 13:28:36 -0700168 dstBounds,
169 &inputBounds,
170 sigma.x(),
171 sigma.y()));
Brian Osman11052242016-10-27 14:47:55 -0400172 if (!renderTargetContext) {
robertphillips1579e3c2016-03-24 05:01:23 -0700173 return nullptr;
174 }
175
Robert Phillips51d77ff2016-12-02 14:56:45 -0500176 return SkSpecialImage::MakeFromGpu(SkIRect::MakeWH(dstBounds.width(), dstBounds.height()),
robertphillips1579e3c2016-03-24 05:01:23 -0700177 kNeedNewImageUniqueID_SpecialImage,
Robert Phillips51d77ff2016-12-02 14:56:45 -0500178 renderTargetContext->asTexture(),
Brian Osman5f500922016-12-29 11:50:48 -0500179 sk_ref_sp(renderTargetContext->getColorSpace()),
180 &source->props());
robertphillips1579e3c2016-03-24 05:01:23 -0700181 }
182#endif
senorblanco@chromium.org2bfe36b2014-01-20 19:58:28 +0000183
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000184 int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
185 int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
robertphillips1579e3c2016-03-24 05:01:23 -0700186 get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
187 get_box3_params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000188
189 if (kernelSizeX < 0 || kernelSizeY < 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700190 return nullptr;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000191 }
192
193 if (kernelSizeX == 0 && kernelSizeY == 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700194 offset->fX = inputBounds.x();
195 offset->fY = inputBounds.y();
196 return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(),
robertphillips2302de92016-03-24 07:26:32 -0700197 -inputOffset.y()));
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000198 }
199
robertphillips64612512016-04-08 12:10:42 -0700200 SkBitmap inputBM;
robertphillips1579e3c2016-03-24 05:01:23 -0700201
robertphillips64612512016-04-08 12:10:42 -0700202 if (!input->getROPixels(&inputBM)) {
robertphillips1579e3c2016-03-24 05:01:23 -0700203 return nullptr;
senorblanco64049812016-02-01 10:32:42 -0800204 }
205
robertphillips64612512016-04-08 12:10:42 -0700206 if (inputBM.colorType() != kN32_SkColorType) {
robertphillips1579e3c2016-03-24 05:01:23 -0700207 return nullptr;
senorblanco64049812016-02-01 10:32:42 -0800208 }
senorblanco64049812016-02-01 10:32:42 -0800209
robertphillips1579e3c2016-03-24 05:01:23 -0700210 SkImageInfo info = SkImageInfo::Make(dstBounds.width(), dstBounds.height(),
robertphillips64612512016-04-08 12:10:42 -0700211 inputBM.colorType(), inputBM.alphaType());
robertphillips1579e3c2016-03-24 05:01:23 -0700212
213 SkBitmap tmp, dst;
214 if (!tmp.tryAllocPixels(info) || !dst.tryAllocPixels(info)) {
215 return nullptr;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000216 }
robertphillips1579e3c2016-03-24 05:01:23 -0700217
robertphillips64612512016-04-08 12:10:42 -0700218 SkAutoLockPixels inputLock(inputBM), tmpLock(tmp), dstLock(dst);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000219
senorblanco29a440d2015-11-02 12:55:47 -0800220 offset->fX = dstBounds.fLeft;
221 offset->fY = dstBounds.fTop;
robertphillips1579e3c2016-03-24 05:01:23 -0700222 SkPMColor* t = tmp.getAddr32(0, 0);
223 SkPMColor* d = dst.getAddr32(0, 0);
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000224 int w = dstBounds.width(), h = dstBounds.height();
robertphillips64612512016-04-08 12:10:42 -0700225 const SkPMColor* s = inputBM.getAddr32(inputBounds.x() - inputOffset.x(),
226 inputBounds.y() - inputOffset.y());
robertphillips1579e3c2016-03-24 05:01:23 -0700227 inputBounds.offset(-dstBounds.x(), -dstBounds.y());
senorblanco29a440d2015-11-02 12:55:47 -0800228 dstBounds.offset(-dstBounds.x(), -dstBounds.y());
robertphillips1579e3c2016-03-24 05:01:23 -0700229 SkIRect inputBoundsT = SkIRect::MakeLTRB(inputBounds.top(), inputBounds.left(),
230 inputBounds.bottom(), inputBounds.right());
senorblanco29a440d2015-11-02 12:55:47 -0800231 SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width());
robertphillips64612512016-04-08 12:10:42 -0700232 int sw = int(inputBM.rowBytes() >> 2);
senorblanco@chromium.org27eec462013-11-08 20:49:04 +0000233
mtkleindce5ce42015-08-04 08:49:21 -0700234 /**
235 *
236 * In order to make memory accesses cache-friendly, we reorder the passes to
237 * use contiguous memory reads wherever possible.
238 *
239 * For example, the 6 passes of the X-and-Y blur case are rewritten as
240 * follows. Instead of 3 passes in X and 3 passes in Y, we perform
241 * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X,
242 * then 1 pass in X transposed to Y on write.
243 *
244 * +----+ +----+ +----+ +---+ +---+ +---+ +----+
245 * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB |
246 * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+
247 * +---+ +---+ +---+
248 *
249 * In this way, two of the y-blurs become x-blurs applied to transposed
250 * images, and all memory reads are contiguous.
251 */
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000252 if (kernelSizeX > 0 && kernelSizeY > 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700253 SkOpts::box_blur_xx(s, sw, inputBounds, t, kernelSizeX, lowOffsetX, highOffsetX, w, h);
254 SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX, highOffsetX, lowOffsetX, w, h);
255 SkOpts::box_blur_xy(d, w, dstBounds, t, kernelSizeX3, highOffsetX, highOffsetX, w, h);
256 SkOpts::box_blur_xx(t, h, dstBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
257 SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
258 SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000259 } else if (kernelSizeX > 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700260 SkOpts::box_blur_xx(s, sw, inputBounds, d, kernelSizeX, lowOffsetX, highOffsetX, w, h);
261 SkOpts::box_blur_xx(d, w, dstBounds, t, kernelSizeX, highOffsetX, lowOffsetX, w, h);
262 SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX3, highOffsetX, highOffsetX, w, h);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000263 } else if (kernelSizeY > 0) {
robertphillips1579e3c2016-03-24 05:01:23 -0700264 SkOpts::box_blur_yx(s, sw, inputBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
265 SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
266 SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000267 }
robertphillips1579e3c2016-03-24 05:01:23 -0700268
robertphillips3e302272016-04-20 11:48:36 -0700269 return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(),
robertphillips1579e3c2016-03-24 05:01:23 -0700270 dstBounds.height()),
brianosman898235c2016-04-06 07:38:23 -0700271 dst, &source->props());
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000272}
273
senorblanco@chromium.org336d1d72014-01-27 21:03:17 +0000274
vjiaoblacke1e5c742016-08-23 11:13:14 -0700275SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const {
senorblancoe5e79842016-03-21 14:51:59 -0700276 SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
277 bounds.outset(SkScalarMul(fSigma.width(), SkIntToScalar(3)),
278 SkScalarMul(fSigma.height(), SkIntToScalar(3)));
279 return bounds;
senorblanco@chromium.org336d1d72014-01-27 21:03:17 +0000280}
senorblanco@chromium.orgc4b12f12014-02-05 17:51:22 +0000281
vjiaoblacke1e5c742016-08-23 11:13:14 -0700282SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
senorblancoe5e79842016-03-21 14:51:59 -0700283 MapDirection) const {
senorblanco64049812016-02-01 10:32:42 -0800284 SkVector sigma = map_sigma(fSigma, ctm);
senorblancoe5e79842016-03-21 14:51:59 -0700285 return src.makeOutset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
286 SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
senorblanco@chromium.orgc4b12f12014-02-05 17:51:22 +0000287}
288
robertphillipsf3f5bad2014-12-19 13:49:15 -0800289#ifndef SK_IGNORE_TO_STRING
vjiaoblacke1e5c742016-08-23 11:13:14 -0700290void SkBlurImageFilterImpl::toString(SkString* str) const {
291 str->appendf("SkBlurImageFilterImpl: (");
robertphillips33cca882014-12-22 06:52:04 -0800292 str->appendf("sigma: (%f, %f) input (", fSigma.fWidth, fSigma.fHeight);
293
294 if (this->getInput(0)) {
295 this->getInput(0)->toString(str);
296 }
297
298 str->append("))");
robertphillipsf3f5bad2014-12-19 13:49:15 -0800299}
300#endif