blob: d08209874bdad74be11d035fd8f2ac9cf976725d [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
djsollen@google.com64a0ec32012-06-12 15:17:27 +00008#include "SkBitmap.h"
senorblanco@chromium.org60014ca2011-11-09 16:05:58 +00009#include "SkBlurImageFilter.h"
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +000010#include "SkColorPriv.h"
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +000011#include "SkReadBuffer.h"
12#include "SkWriteBuffer.h"
robertphillips@google.com736dd032013-07-15 15:06:54 +000013#include "SkGpuBlurUtils.h"
senorblanco@chromium.org27eec462013-11-08 20:49:04 +000014#include "SkBlurImage_opts.h"
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +000015#if SK_SUPPORT_GPU
senorblanco@chromium.org302cffb2012-08-01 20:16:34 +000016#include "GrContext.h"
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +000017#endif
senorblanco@chromium.org60014ca2011-11-09 16:05:58 +000018
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +000019// This rather arbitrary-looking value results in a maximum box blur kernel size
20// of 1000 pixels on the raster path, which matches the WebKit and Firefox
21// implementations. Since the GPU path does not compute a box blur, putting
22// the limit on sigma ensures consistent behaviour between the GPU and
23// raster paths.
24#define MAX_SIGMA SkIntToScalar(532)
25
reed9fa60da2014-08-21 07:59:51 -070026#ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +000027SkBlurImageFilter::SkBlurImageFilter(SkReadBuffer& buffer)
commit-bot@chromium.orgce33d602013-11-25 21:46:31 +000028 : INHERITED(1, buffer) {
senorblanco@chromium.org54e01b22011-11-16 18:20:47 +000029 fSigma.fWidth = buffer.readScalar();
30 fSigma.fHeight = buffer.readScalar();
commit-bot@chromium.orgc0b7e102013-10-23 17:06:21 +000031 buffer.validate(SkScalarIsFinite(fSigma.fWidth) &&
32 SkScalarIsFinite(fSigma.fHeight) &&
33 (fSigma.fWidth >= 0) &&
34 (fSigma.fHeight >= 0));
senorblanco@chromium.org54e01b22011-11-16 18:20:47 +000035}
reed9fa60da2014-08-21 07:59:51 -070036#endif
senorblanco@chromium.org54e01b22011-11-16 18:20:47 +000037
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000038SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX,
39 SkScalar sigmaY,
40 SkImageFilter* input,
senorblanco5e5f9482014-08-26 12:27:12 -070041 const CropRect* cropRect,
42 uint32_t uniqueID)
43 : INHERITED(1, &input, cropRect, uniqueID), fSigma(SkSize::Make(sigmaX, sigmaY)) {
senorblanco@chromium.org60014ca2011-11-09 16:05:58 +000044}
45
reed9fa60da2014-08-21 07:59:51 -070046SkFlattenable* SkBlurImageFilter::CreateProc(SkReadBuffer& buffer) {
47 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
48 SkScalar sigmaX = buffer.readScalar();
49 SkScalar sigmaY = buffer.readScalar();
senorblanco5e5f9482014-08-26 12:27:12 -070050 return Create(sigmaX, sigmaY, common.getInput(0), &common.cropRect(), common.uniqueID());
reed9fa60da2014-08-21 07:59:51 -070051}
52
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +000053void SkBlurImageFilter::flatten(SkWriteBuffer& buffer) const {
senorblanco@chromium.org54e01b22011-11-16 18:20:47 +000054 this->INHERITED::flatten(buffer);
55 buffer.writeScalar(fSigma.fWidth);
56 buffer.writeScalar(fSigma.fHeight);
57}
58
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +000059enum BlurDirection {
60 kX, kY
61};
62
63/**
64 *
65 * In order to make memory accesses cache-friendly, we reorder the passes to
66 * use contiguous memory reads wherever possible.
67 *
68 * For example, the 6 passes of the X-and-Y blur case are rewritten as
69 * follows. Instead of 3 passes in X and 3 passes in Y, we perform
70 * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X,
71 * then 1 pass in X transposed to Y on write.
72 *
73 * +----+ +----+ +----+ +---+ +---+ +---+ +----+
74 * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB |
75 * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+
76 * +---+ +---+ +---+
77 *
78 * In this way, two of the y-blurs become x-blurs applied to transposed
79 * images, and all memory reads are contiguous.
80 */
81
82template<BlurDirection srcDirection, BlurDirection dstDirection>
83static void boxBlur(const SkPMColor* src, int srcStride, SkPMColor* dst, int kernelSize,
84 int leftOffset, int rightOffset, int width, int height)
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +000085{
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +000086 int rightBorder = SkMin32(rightOffset + 1, width);
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +000087 int srcStrideX = srcDirection == kX ? 1 : srcStride;
88 int dstStrideX = dstDirection == kX ? 1 : height;
89 int srcStrideY = srcDirection == kX ? srcStride : 1;
90 int dstStrideY = dstDirection == kX ? width : 1;
senorblanco@chromium.orgfe2faa82013-11-04 16:07:33 +000091 uint32_t scale = (1 << 24) / kernelSize;
92 uint32_t half = 1 << 23;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +000093 for (int y = 0; y < height; ++y) {
94 int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +000095 const SkPMColor* p = src;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +000096 for (int i = 0; i < rightBorder; ++i) {
97 sumA += SkGetPackedA32(*p);
98 sumR += SkGetPackedR32(*p);
99 sumG += SkGetPackedG32(*p);
100 sumB += SkGetPackedB32(*p);
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000101 p += srcStrideX;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000102 }
103
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000104 const SkPMColor* sptr = src;
105 SkColor* dptr = dst;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000106 for (int x = 0; x < width; ++x) {
senorblanco@chromium.orgfe2faa82013-11-04 16:07:33 +0000107 *dptr = SkPackARGB32((sumA * scale + half) >> 24,
108 (sumR * scale + half) >> 24,
109 (sumG * scale + half) >> 24,
110 (sumB * scale + half) >> 24);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000111 if (x >= leftOffset) {
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000112 SkColor l = *(sptr - leftOffset * srcStrideX);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000113 sumA -= SkGetPackedA32(l);
114 sumR -= SkGetPackedR32(l);
115 sumG -= SkGetPackedG32(l);
116 sumB -= SkGetPackedB32(l);
117 }
118 if (x + rightOffset + 1 < width) {
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000119 SkColor r = *(sptr + (rightOffset + 1) * srcStrideX);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000120 sumA += SkGetPackedA32(r);
121 sumR += SkGetPackedR32(r);
122 sumG += SkGetPackedG32(r);
123 sumB += SkGetPackedB32(r);
124 }
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000125 sptr += srcStrideX;
126 if (srcDirection == kY) {
127 SK_PREFETCH(sptr + (rightOffset + 1) * srcStrideX);
128 }
129 dptr += dstStrideX;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000130 }
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000131 src += srcStrideY;
132 dst += dstStrideY;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000133 }
134}
135
commit-bot@chromium.org7b320702013-07-10 21:22:18 +0000136static void getBox3Params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
137 int *highOffset)
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000138{
schenney@chromium.org73f3ded2011-12-20 22:31:40 +0000139 float pi = SkScalarToFloat(SK_ScalarPI);
140 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 +0000141 *kernelSize = d;
142 if (d % 2 == 1) {
143 *lowOffset = *highOffset = (d - 1) / 2;
144 *kernelSize3 = d;
145 } else {
146 *highOffset = d / 2;
147 *lowOffset = *highOffset - 1;
148 *kernelSize3 = d + 1;
149 }
150}
151
senorblanco@chromium.orgf1369ce2012-08-20 14:53:21 +0000152bool SkBlurImageFilter::onFilterImage(Proxy* proxy,
senorblanco@chromium.org4cb543d2014-03-14 15:44:01 +0000153 const SkBitmap& source, const Context& ctx,
commit-bot@chromium.orgae761f72014-02-05 22:32:02 +0000154 SkBitmap* dst, SkIPoint* offset) const {
senorblanco@chromium.org68400762013-05-24 15:04:07 +0000155 SkBitmap src = source;
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000156 SkIPoint srcOffset = SkIPoint::Make(0, 0);
senorblanco@chromium.org4cb543d2014-03-14 15:44:01 +0000157 if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctx, &src, &srcOffset)) {
senorblanco@chromium.org68400762013-05-24 15:04:07 +0000158 return false;
159 }
160
commit-bot@chromium.org28fcae22014-04-11 17:15:40 +0000161 if (src.colorType() != kN32_SkColorType) {
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000162 return false;
163 }
164
senorblanco@chromium.org11825292014-03-14 17:44:41 +0000165 SkIRect srcBounds, dstBounds;
166 if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &srcBounds, &src)) {
reed@google.com76dd2772012-01-05 21:15:07 +0000167 return false;
168 }
169
senorblanco@chromium.org11825292014-03-14 17:44:41 +0000170 SkAutoLockPixels alp(src);
171 if (!src.getPixels()) {
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000172 return false;
173 }
174
reedc77392e2014-06-02 13:07:26 -0700175 if (!dst->allocPixels(src.info().makeWH(srcBounds.width(), srcBounds.height()))) {
commit-bot@chromium.orgcd3b15c2013-12-04 17:06:49 +0000176 return false;
177 }
reedc77392e2014-06-02 13:07:26 -0700178 dst->getBounds(&dstBounds);
commit-bot@chromium.orgcd3b15c2013-12-04 17:06:49 +0000179
senorblanco@chromium.orgba31f1d2014-05-07 20:56:08 +0000180 SkVector sigma = SkVector::Make(fSigma.width(), fSigma.height());
181 ctx.ctm().mapVectors(&sigma, 1);
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +0000182 sigma.fX = SkMinScalar(sigma.fX, MAX_SIGMA);
183 sigma.fY = SkMinScalar(sigma.fY, MAX_SIGMA);
senorblanco@chromium.org2bfe36b2014-01-20 19:58:28 +0000184
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000185 int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
186 int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
senorblanco@chromium.org2bfe36b2014-01-20 19:58:28 +0000187 getBox3Params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
188 getBox3Params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000189
190 if (kernelSizeX < 0 || kernelSizeY < 0) {
191 return false;
192 }
193
194 if (kernelSizeX == 0 && kernelSizeY == 0) {
commit-bot@chromium.org8a2ad3c2014-02-23 03:59:35 +0000195 src.copyTo(dst, dst->colorType());
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000196 offset->fX = srcBounds.fLeft;
197 offset->fY = srcBounds.fTop;
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000198 return true;
199 }
200
201 SkBitmap temp;
reedc77392e2014-06-02 13:07:26 -0700202 if (!temp.allocPixels(dst->info())) {
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000203 return false;
204 }
205
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000206 offset->fX = srcBounds.fLeft;
207 offset->fY = srcBounds.fTop;
208 srcBounds.offset(-srcOffset);
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000209 const SkPMColor* s = src.getAddr32(srcBounds.left(), srcBounds.top());
210 SkPMColor* t = temp.getAddr32(0, 0);
211 SkPMColor* d = dst->getAddr32(0, 0);
212 int w = dstBounds.width(), h = dstBounds.height();
213 int sw = src.rowBytesAsPixels();
senorblanco@chromium.org05edd022013-11-11 20:12:34 +0000214 SkBoxBlurProc boxBlurX, boxBlurY, boxBlurXY, boxBlurYX;
215 if (!SkBoxBlurGetPlatformProcs(&boxBlurX, &boxBlurY, &boxBlurXY, &boxBlurYX)) {
senorblanco@chromium.org27eec462013-11-08 20:49:04 +0000216 boxBlurX = boxBlur<kX, kX>;
217 boxBlurY = boxBlur<kY, kY>;
218 boxBlurXY = boxBlur<kX, kY>;
senorblanco@chromium.org05edd022013-11-11 20:12:34 +0000219 boxBlurYX = boxBlur<kY, kX>;
senorblanco@chromium.org27eec462013-11-08 20:49:04 +0000220 }
221
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000222 if (kernelSizeX > 0 && kernelSizeY > 0) {
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000223 boxBlurX(s, sw, t, kernelSizeX, lowOffsetX, highOffsetX, w, h);
224 boxBlurX(t, w, d, kernelSizeX, highOffsetX, lowOffsetX, w, h);
225 boxBlurXY(d, w, t, kernelSizeX3, highOffsetX, highOffsetX, w, h);
226 boxBlurX(t, h, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
227 boxBlurX(d, h, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
228 boxBlurXY(t, h, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000229 } else if (kernelSizeX > 0) {
senorblanco@chromium.org0cc00c22013-11-07 18:35:12 +0000230 boxBlurX(s, sw, d, kernelSizeX, lowOffsetX, highOffsetX, w, h);
231 boxBlurX(d, w, t, kernelSizeX, highOffsetX, lowOffsetX, w, h);
232 boxBlurX(t, w, d, kernelSizeX3, highOffsetX, highOffsetX, w, h);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000233 } else if (kernelSizeY > 0) {
senorblanco@chromium.org05edd022013-11-11 20:12:34 +0000234 boxBlurYX(s, sw, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
235 boxBlurX(d, h, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
236 boxBlurXY(t, h, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
senorblanco@chromium.orgae814c72011-12-20 20:02:19 +0000237 }
238 return true;
239}
240
senorblanco@chromium.org336d1d72014-01-27 21:03:17 +0000241
242void SkBlurImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
243 if (getInput(0)) {
244 getInput(0)->computeFastBounds(src, dst);
245 } else {
246 *dst = src;
247 }
248
249 dst->outset(SkScalarMul(fSigma.width(), SkIntToScalar(3)),
250 SkScalarMul(fSigma.height(), SkIntToScalar(3)));
251}
senorblanco@chromium.orgc4b12f12014-02-05 17:51:22 +0000252
253bool SkBlurImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
254 SkIRect* dst) const {
255 SkIRect bounds = src;
senorblanco@chromium.orgba31f1d2014-05-07 20:56:08 +0000256 SkVector sigma = SkVector::Make(fSigma.width(), fSigma.height());
257 ctm.mapVectors(&sigma, 1);
senorblanco@chromium.orgc4b12f12014-02-05 17:51:22 +0000258 bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
259 SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
senorblanco1150a6d2014-08-25 12:46:58 -0700260 if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
261 return false;
262 }
senorblanco@chromium.orgc4b12f12014-02-05 17:51:22 +0000263 *dst = bounds;
264 return true;
265}
266
senorblanco@chromium.org4cb543d2014-03-14 15:44:01 +0000267bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx,
commit-bot@chromium.orgae761f72014-02-05 22:32:02 +0000268 SkBitmap* result, SkIPoint* offset) const {
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000269#if SK_SUPPORT_GPU
senorblanco@chromium.org6aa6fec2014-03-03 22:13:56 +0000270 SkBitmap input = src;
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000271 SkIPoint srcOffset = SkIPoint::Make(0, 0);
senorblanco@chromium.org4cb543d2014-03-14 15:44:01 +0000272 if (getInput(0) && !getInput(0)->getInputResultGPU(proxy, src, ctx, &input, &srcOffset)) {
senorblanco@chromium.orgc2594f42013-01-30 19:08:47 +0000273 return false;
274 }
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000275 SkIRect rect;
senorblanco@chromium.org11825292014-03-14 17:44:41 +0000276 if (!this->applyCropRect(ctx, proxy, input, &srcOffset, &rect, &input)) {
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000277 return false;
278 }
senorblanco@chromium.org11825292014-03-14 17:44:41 +0000279 GrTexture* source = input.getTexture();
senorblanco@chromium.orgba31f1d2014-05-07 20:56:08 +0000280 SkVector sigma = SkVector::Make(fSigma.width(), fSigma.height());
281 ctx.ctm().mapVectors(&sigma, 1);
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +0000282 sigma.fX = SkMinScalar(sigma.fX, MAX_SIGMA);
283 sigma.fY = SkMinScalar(sigma.fY, MAX_SIGMA);
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000284 offset->fX = rect.fLeft;
285 offset->fY = rect.fTop;
286 rect.offset(-srcOffset);
skia.committer@gmail.com6ae63832013-07-23 07:01:05 +0000287 SkAutoTUnref<GrTexture> tex(SkGpuBlurUtils::GaussianBlur(source->getContext(),
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000288 source,
289 false,
290 SkRect::Make(rect),
291 true,
senorblanco@chromium.org2bfe36b2014-01-20 19:58:28 +0000292 sigma.x(),
293 sigma.y()));
senorblanco@chromium.org6aa6fec2014-03-03 22:13:56 +0000294 WrapTexture(tex, rect.width(), rect.height(), result);
295 return true;
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000296#else
297 SkDEBUGFAIL("Should not call in GPU-less build");
senorblanco@chromium.orgc2594f42013-01-30 19:08:47 +0000298 return false;
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000299#endif
senorblanco@chromium.org302cffb2012-08-01 20:16:34 +0000300}