1/*
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
8#include "SkBitmap.h"
9#include "SkBlurImageFilter.h"
10#include "SkColorPriv.h"
11#include "SkReadBuffer.h"
12#include "SkWriteBuffer.h"
13#include "SkGpuBlurUtils.h"
14#include "SkBlurImage_opts.h"
15#if SK_SUPPORT_GPU
16#include "GrContext.h"
17#endif
18
19// 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
26static SkVector mapSigma(const SkSize& localSigma, const SkMatrix& ctm) {
27    SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height());
28    ctm.mapVectors(&sigma, 1);
29    sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA);
30    sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA);
31    return sigma;
32}
33
34#ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
35SkBlurImageFilter::SkBlurImageFilter(SkReadBuffer& buffer)
36  : INHERITED(1, buffer) {
37    fSigma.fWidth = buffer.readScalar();
38    fSigma.fHeight = buffer.readScalar();
39    buffer.validate(SkScalarIsFinite(fSigma.fWidth) &&
40                    SkScalarIsFinite(fSigma.fHeight) &&
41                    (fSigma.fWidth >= 0) &&
42                    (fSigma.fHeight >= 0));
43}
44#endif
45
46SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX,
47                                     SkScalar sigmaY,
48                                     SkImageFilter* input,
49                                     const CropRect* cropRect,
50                                     uint32_t uniqueID)
51    : INHERITED(1, &input, cropRect, uniqueID), fSigma(SkSize::Make(sigmaX, sigmaY)) {
52}
53
54SkFlattenable* SkBlurImageFilter::CreateProc(SkReadBuffer& buffer) {
55    SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
56    SkScalar sigmaX = buffer.readScalar();
57    SkScalar sigmaY = buffer.readScalar();
58    return Create(sigmaX, sigmaY, common.getInput(0), &common.cropRect(), common.uniqueID());
59}
60
61void SkBlurImageFilter::flatten(SkWriteBuffer& buffer) const {
62    this->INHERITED::flatten(buffer);
63    buffer.writeScalar(fSigma.fWidth);
64    buffer.writeScalar(fSigma.fHeight);
65}
66
67enum BlurDirection {
68    kX, kY
69};
70
71/**
72 *
73 * In order to make memory accesses cache-friendly, we reorder the passes to
74 * use contiguous memory reads wherever possible.
75 *
76 * For example, the 6 passes of the X-and-Y blur case are rewritten as
77 * follows. Instead of 3 passes in X and 3 passes in Y, we perform
78 * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X,
79 * then 1 pass in X transposed to Y on write.
80 *
81 * +----+       +----+       +----+        +---+       +---+       +---+        +----+
82 * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB |
83 * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+
84 *                                         +---+       +---+       +---+
85 *
86 * In this way, two of the y-blurs become x-blurs applied to transposed
87 * images, and all memory reads are contiguous.
88 */
89
90template<BlurDirection srcDirection, BlurDirection dstDirection>
91static void boxBlur(const SkPMColor* src, int srcStride, SkPMColor* dst, int kernelSize,
92                    int leftOffset, int rightOffset, int width, int height)
93{
94    int rightBorder = SkMin32(rightOffset + 1, width);
95    int srcStrideX = srcDirection == kX ? 1 : srcStride;
96    int dstStrideX = dstDirection == kX ? 1 : height;
97    int srcStrideY = srcDirection == kX ? srcStride : 1;
98    int dstStrideY = dstDirection == kX ? width : 1;
99    uint32_t scale = (1 << 24) / kernelSize;
100    uint32_t half = 1 << 23;
101    for (int y = 0; y < height; ++y) {
102        int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
103        const SkPMColor* p = src;
104        for (int i = 0; i < rightBorder; ++i) {
105            sumA += SkGetPackedA32(*p);
106            sumR += SkGetPackedR32(*p);
107            sumG += SkGetPackedG32(*p);
108            sumB += SkGetPackedB32(*p);
109            p += srcStrideX;
110        }
111
112        const SkPMColor* sptr = src;
113        SkColor* dptr = dst;
114        for (int x = 0; x < width; ++x) {
115            *dptr = SkPackARGB32((sumA * scale + half) >> 24,
116                                 (sumR * scale + half) >> 24,
117                                 (sumG * scale + half) >> 24,
118                                 (sumB * scale + half) >> 24);
119            if (x >= leftOffset) {
120                SkColor l = *(sptr - leftOffset * srcStrideX);
121                sumA -= SkGetPackedA32(l);
122                sumR -= SkGetPackedR32(l);
123                sumG -= SkGetPackedG32(l);
124                sumB -= SkGetPackedB32(l);
125            }
126            if (x + rightOffset + 1 < width) {
127                SkColor r = *(sptr + (rightOffset + 1) * srcStrideX);
128                sumA += SkGetPackedA32(r);
129                sumR += SkGetPackedR32(r);
130                sumG += SkGetPackedG32(r);
131                sumB += SkGetPackedB32(r);
132            }
133            sptr += srcStrideX;
134            if (srcDirection == kY) {
135                SK_PREFETCH(sptr + (rightOffset + 1) * srcStrideX);
136            }
137            dptr += dstStrideX;
138        }
139        src += srcStrideY;
140        dst += dstStrideY;
141    }
142}
143
144static void getBox3Params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
145                          int *highOffset)
146{
147    float pi = SkScalarToFloat(SK_ScalarPI);
148    int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
149    *kernelSize = d;
150    if (d % 2 == 1) {
151        *lowOffset = *highOffset = (d - 1) / 2;
152        *kernelSize3 = d;
153    } else {
154        *highOffset = d / 2;
155        *lowOffset = *highOffset - 1;
156        *kernelSize3 = d + 1;
157    }
158}
159
160bool SkBlurImageFilter::onFilterImage(Proxy* proxy,
161                                      const SkBitmap& source, const Context& ctx,
162                                      SkBitmap* dst, SkIPoint* offset) const {
163    SkBitmap src = source;
164    SkIPoint srcOffset = SkIPoint::Make(0, 0);
165    if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctx, &src, &srcOffset)) {
166        return false;
167    }
168
169    if (src.colorType() != kN32_SkColorType) {
170        return false;
171    }
172
173    SkIRect srcBounds, dstBounds;
174    if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &srcBounds, &src)) {
175        return false;
176    }
177
178    SkAutoLockPixels alp(src);
179    if (!src.getPixels()) {
180        return false;
181    }
182
183    if (!dst->tryAllocPixels(src.info().makeWH(srcBounds.width(), srcBounds.height()))) {
184        return false;
185    }
186    dst->getBounds(&dstBounds);
187
188    SkVector sigma = mapSigma(fSigma, ctx.ctm());
189
190    int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
191    int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
192    getBox3Params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
193    getBox3Params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
194
195    if (kernelSizeX < 0 || kernelSizeY < 0) {
196        return false;
197    }
198
199    if (kernelSizeX == 0 && kernelSizeY == 0) {
200        src.copyTo(dst, dst->colorType());
201        offset->fX = srcBounds.fLeft;
202        offset->fY = srcBounds.fTop;
203        return true;
204    }
205
206    SkBitmap temp;
207    if (!temp.tryAllocPixels(dst->info())) {
208        return false;
209    }
210
211    offset->fX = srcBounds.fLeft;
212    offset->fY = srcBounds.fTop;
213    srcBounds.offset(-srcOffset);
214    const SkPMColor* s = src.getAddr32(srcBounds.left(), srcBounds.top());
215    SkPMColor* t = temp.getAddr32(0, 0);
216    SkPMColor* d = dst->getAddr32(0, 0);
217    int w = dstBounds.width(), h = dstBounds.height();
218    int sw = src.rowBytesAsPixels();
219    SkBoxBlurProc boxBlurX, boxBlurY, boxBlurXY, boxBlurYX;
220    if (!SkBoxBlurGetPlatformProcs(&boxBlurX, &boxBlurY, &boxBlurXY, &boxBlurYX)) {
221        boxBlurX = boxBlur<kX, kX>;
222        boxBlurY = boxBlur<kY, kY>;
223        boxBlurXY = boxBlur<kX, kY>;
224        boxBlurYX = boxBlur<kY, kX>;
225    }
226
227    if (kernelSizeX > 0 && kernelSizeY > 0) {
228        boxBlurX(s,  sw, t, kernelSizeX,  lowOffsetX,  highOffsetX, w, h);
229        boxBlurX(t,  w,  d, kernelSizeX,  highOffsetX, lowOffsetX,  w, h);
230        boxBlurXY(d, w,  t, kernelSizeX3, highOffsetX, highOffsetX, w, h);
231        boxBlurX(t,  h,  d, kernelSizeY,  lowOffsetY,  highOffsetY, h, w);
232        boxBlurX(d,  h,  t, kernelSizeY,  highOffsetY, lowOffsetY,  h, w);
233        boxBlurXY(t, h,  d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
234    } else if (kernelSizeX > 0) {
235        boxBlurX(s,  sw, d, kernelSizeX,  lowOffsetX,  highOffsetX, w, h);
236        boxBlurX(d,  w,  t, kernelSizeX,  highOffsetX, lowOffsetX,  w, h);
237        boxBlurX(t,  w,  d, kernelSizeX3, highOffsetX, highOffsetX, w, h);
238    } else if (kernelSizeY > 0) {
239        boxBlurYX(s, sw, d, kernelSizeY,  lowOffsetY,  highOffsetY, h, w);
240        boxBlurX(d,  h,  t, kernelSizeY,  highOffsetY, lowOffsetY,  h, w);
241        boxBlurXY(t, h,  d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
242    }
243    return true;
244}
245
246
247void SkBlurImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
248    if (getInput(0)) {
249        getInput(0)->computeFastBounds(src, dst);
250    } else {
251        *dst = src;
252    }
253
254    dst->outset(SkScalarMul(fSigma.width(), SkIntToScalar(3)),
255                SkScalarMul(fSigma.height(), SkIntToScalar(3)));
256}
257
258bool SkBlurImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
259                                       SkIRect* dst) const {
260    SkIRect bounds = src;
261    SkVector sigma = mapSigma(fSigma, ctm);
262    bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
263                  SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
264    if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
265        return false;
266    }
267    *dst = bounds;
268    return true;
269}
270
271bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx,
272                                       SkBitmap* result, SkIPoint* offset) const {
273#if SK_SUPPORT_GPU
274    SkBitmap input = src;
275    SkIPoint srcOffset = SkIPoint::Make(0, 0);
276    if (getInput(0) && !getInput(0)->getInputResultGPU(proxy, src, ctx, &input, &srcOffset)) {
277        return false;
278    }
279    SkIRect rect;
280    if (!this->applyCropRect(ctx, proxy, input, &srcOffset, &rect, &input)) {
281        return false;
282    }
283    GrTexture* source = input.getTexture();
284    SkVector sigma = mapSigma(fSigma, ctx.ctm());
285    offset->fX = rect.fLeft;
286    offset->fY = rect.fTop;
287    rect.offset(-srcOffset);
288    SkAutoTUnref<GrTexture> tex(SkGpuBlurUtils::GaussianBlur(source->getContext(),
289                                                             source,
290                                                             false,
291                                                             SkRect::Make(rect),
292                                                             true,
293                                                             sigma.x(),
294                                                             sigma.y()));
295    WrapTexture(tex, rect.width(), rect.height(), result);
296    return true;
297#else
298    SkDEBUGFAIL("Should not call in GPU-less build");
299    return false;
300#endif
301}
302