SkBlurImageFilter.cpp revision 5a4fb6eb335d29f24a1b239d1c0eb11b5d2ee59f
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 "SkBlurImageFilter.h"
9
10#include "SkAutoPixmapStorage.h"
11#include "SkColorData.h"
12#include "SkColorSpaceXformer.h"
13#include "SkTFitsIn.h"
14#include "SkGpuBlurUtils.h"
15#include "SkOpts.h"
16#include "SkReadBuffer.h"
17#include "SkSpecialImage.h"
18#include "SkWriteBuffer.h"
19
20#if SK_SUPPORT_GPU
21#include "GrContext.h"
22#include "GrTextureProxy.h"
23#include "SkGr.h"
24#endif
25
26class SkBlurImageFilterImpl final : public SkImageFilter {
27public:
28    SkBlurImageFilterImpl(SkScalar sigmaX,
29                      SkScalar sigmaY,
30                      sk_sp<SkImageFilter> input,
31                      const CropRect* cropRect,
32                      SkBlurImageFilter::TileMode tileMode);
33
34    SkRect computeFastBounds(const SkRect&) const override;
35
36    SK_TO_STRING_OVERRIDE()
37    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurImageFilterImpl)
38
39protected:
40    void flatten(SkWriteBuffer&) const override;
41    sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
42                                        SkIPoint* offset) const override;
43    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
44    SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override;
45
46private:
47    typedef SkImageFilter INHERITED;
48    friend class SkImageFilter;
49
50    #if SK_SUPPORT_GPU
51    sk_sp<SkSpecialImage> gpuFilter(
52            SkSpecialImage *source,
53            SkVector sigma, const sk_sp<SkSpecialImage> &input,
54            SkIRect inputBounds, SkIRect dstBounds) const;
55    #endif
56
57    sk_sp<SkSpecialImage> cpuFilter(
58            SkSpecialImage *source,
59            SkVector sigma, const sk_sp<SkSpecialImage> &input,
60            SkIRect inputBounds, SkIRect dstBounds) const;
61
62    SkSize                      fSigma;
63    SkBlurImageFilter::TileMode fTileMode;
64};
65
66SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter)
67    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl)
68SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
69
70///////////////////////////////////////////////////////////////////////////////
71
72sk_sp<SkImageFilter> SkBlurImageFilter::Make(SkScalar sigmaX, SkScalar sigmaY,
73                                             sk_sp<SkImageFilter> input,
74                                             const SkImageFilter::CropRect* cropRect,
75                                             TileMode tileMode) {
76    if (0 == sigmaX && 0 == sigmaY && !cropRect) {
77        return input;
78    }
79    return sk_sp<SkImageFilter>(
80          new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect, tileMode));
81}
82
83// This rather arbitrary-looking value results in a maximum box blur kernel size
84// of 1000 pixels on the raster path, which matches the WebKit and Firefox
85// implementations. Since the GPU path does not compute a box blur, putting
86// the limit on sigma ensures consistent behaviour between the GPU and
87// raster paths.
88#define MAX_SIGMA SkIntToScalar(532)
89
90static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) {
91    SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height());
92    ctm.mapVectors(&sigma, 1);
93    sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA);
94    sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA);
95    return sigma;
96}
97
98SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX,
99                                             SkScalar sigmaY,
100                                             sk_sp<SkImageFilter> input,
101                                             const CropRect* cropRect,
102                                             SkBlurImageFilter::TileMode tileMode)
103        : INHERITED(&input, 1, cropRect), fSigma{sigmaX, sigmaY}, fTileMode(tileMode) {}
104
105sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
106    SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
107    SkScalar sigmaX = buffer.readScalar();
108    SkScalar sigmaY = buffer.readScalar();
109    SkBlurImageFilter::TileMode tileMode;
110    if (buffer.isVersionLT(SkReadBuffer::kTileModeInBlurImageFilter_Version)) {
111        tileMode = SkBlurImageFilter::kClampToBlack_TileMode;
112    } else {
113        tileMode = static_cast<SkBlurImageFilter::TileMode>(buffer.readInt());
114    }
115
116    static_assert(SkBlurImageFilter::kMax_TileMode == 2, "CreateProc");
117    SkASSERT(tileMode <= SkBlurImageFilter::kMax_TileMode);
118
119    return SkBlurImageFilter::Make(
120          sigmaX, sigmaY, common.getInput(0), &common.cropRect(), tileMode);
121}
122
123void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const {
124    this->INHERITED::flatten(buffer);
125    buffer.writeScalar(fSigma.fWidth);
126    buffer.writeScalar(fSigma.fHeight);
127
128    static_assert(SkBlurImageFilter::kMax_TileMode == 2, "flatten");
129    SkASSERT(fTileMode <= SkBlurImageFilter::kMax_TileMode);
130
131    buffer.writeInt(static_cast<int>(fTileMode));
132}
133
134#if SK_SUPPORT_GPU
135static GrTextureDomain::Mode to_texture_domain_mode(SkBlurImageFilter::TileMode tileMode) {
136    switch (tileMode) {
137      case SkBlurImageFilter::TileMode::kClamp_TileMode:
138        return GrTextureDomain::kClamp_Mode;
139      case SkBlurImageFilter::TileMode::kClampToBlack_TileMode:
140        return GrTextureDomain::kDecal_Mode;
141      case SkBlurImageFilter::TileMode::kRepeat_TileMode:
142        return GrTextureDomain::kRepeat_Mode;
143      default:
144        SK_ABORT("Unsupported tile mode.");
145        return GrTextureDomain::kDecal_Mode;
146    }
147}
148#endif
149
150static void get_box3_params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
151                            int *highOffset) {
152    float pi = SkScalarToFloat(SK_ScalarPI);
153    int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
154    *kernelSize = d;
155    if (d % 2 == 1) {
156        *lowOffset = *highOffset = (d - 1) / 2;
157        *kernelSize3 = d;
158    } else {
159        *highOffset = d / 2;
160        *lowOffset = *highOffset - 1;
161        *kernelSize3 = d + 1;
162    }
163}
164
165sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* source,
166                                                           const Context& ctx,
167                                                           SkIPoint* offset) const {
168    SkIPoint inputOffset = SkIPoint::Make(0, 0);
169
170    sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
171    if (!input) {
172        return nullptr;
173    }
174
175    SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY,
176                                            input->width(), input->height());
177
178    // Calculate the destination bounds.
179    SkIRect dstBounds;
180    if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) {
181        return nullptr;
182    }
183    if (!inputBounds.intersect(dstBounds)) {
184        return nullptr;
185    }
186
187    // Save the offset in preparation to make all rectangles relative to the inputOffset.
188    SkIPoint resultOffset = SkIPoint::Make(dstBounds.fLeft, dstBounds.fTop);
189
190    // Make all bounds relative to the inputOffset.
191    inputBounds.offset(-inputOffset);
192    dstBounds.offset(-inputOffset);
193
194    const SkVector sigma = map_sigma(fSigma, ctx.ctm());
195    if (sigma.x() < 0 || sigma.y() < 0) {
196        return nullptr;
197    }
198
199    sk_sp<SkSpecialImage> result;
200#if SK_SUPPORT_GPU
201    if (source->isTextureBacked()) {
202        // Ensure the input is in the destination's gamut. This saves us from having to do the
203        // xform during the filter itself.
204        input = ImageToColorSpace(input.get(), ctx.outputProperties());
205
206        result = this->gpuFilter(source, sigma, input, inputBounds, dstBounds);
207    } else
208    #endif
209    {
210        #if defined(SK_SUPPORT_LEGACY_BLUR_IMAGE)
211        result = this->cpuFilter(source, sigma, input, inputBounds, dstBounds);
212        #else
213        // The new code will go here.
214        result = this->cpuFilter(source, sigma, input, inputBounds, dstBounds);
215        #endif
216    }
217
218    // Return the resultOffset if the blur succeeded.
219    if (result != nullptr) {
220        *offset = resultOffset;
221    }
222    return result;
223}
224
225#if SK_SUPPORT_GPU
226sk_sp<SkSpecialImage> SkBlurImageFilterImpl::gpuFilter(
227        SkSpecialImage *source,
228        SkVector sigma, const sk_sp<SkSpecialImage> &input,
229        SkIRect inputBounds, SkIRect dstBounds) const
230{
231    // If both sigmas produce arms of the cross that are less than 1/2048, then they
232    // do not contribute to the sum of the filter in a way to change a gamma corrected result.
233    // Let s = 1/(2*sigma^2)
234    // The normalizing value   n = 1 + 4*E^(-s) + 4*E^(-2s)
235    // The raw cross arm value c = E^-s
236    // The normalized cross arm value = c/n
237    // N[Solve[{c/n == 1/2048, sigma > 0}, sigma], 16]
238    static constexpr double kCrossTooSmall = 0.2561130112451658;
239    if (sigma.x() < kCrossTooSmall && sigma.y() < kCrossTooSmall) {
240        return input->makeSubset(inputBounds);
241    }
242
243    GrContext* context = source->getContext();
244
245    sk_sp<GrTextureProxy> inputTexture(input->asTextureProxyRef(context));
246    if (!inputTexture) {
247        return nullptr;
248    }
249
250    // Typically, we would create the RTC with the output's color space (from ctx), but we
251    // always blur in the PixelConfig of the *input*. Those might not be compatible (if they
252    // have different transfer functions). We've already guaranteed that those color spaces
253    // have the same gamut, so in this case, we do everything in the input's color space.
254    sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
255        context,
256        std::move(inputTexture),
257        sk_ref_sp(input->getColorSpace()),
258        dstBounds,
259        inputBounds,
260        sigma.x(),
261        sigma.y(),
262        to_texture_domain_mode(fTileMode)));
263    if (!renderTargetContext) {
264        return nullptr;
265    }
266
267    return SkSpecialImage::MakeDeferredFromGpu(context,
268                                               SkIRect::MakeWH(dstBounds.width(),
269                                                               dstBounds.height()),
270                                               kNeedNewImageUniqueID_SpecialImage,
271                                               renderTargetContext->asTextureProxyRef(),
272                                               renderTargetContext->refColorSpace(),
273                                               &source->props());
274}
275#endif
276
277// TODO: Implement CPU backend for different fTileMode.
278sk_sp<SkSpecialImage> SkBlurImageFilterImpl::cpuFilter(
279        SkSpecialImage *source,
280        SkVector sigma, const sk_sp<SkSpecialImage> &input,
281        SkIRect inputBounds, SkIRect dstBounds) const
282{
283    // If both sigmas will result in a zero width window, there is nothing to do.
284    // N[Solve[sigma*3*Sqrt[2 Pi]/4 == 1/2, sigma], 16]
285    static constexpr double kZeroWindow = 0.2659615202676218;
286    if (sigma.x() < kZeroWindow && sigma.y() < kZeroWindow) {
287        return input->makeSubset(inputBounds);
288    }
289
290    int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
291    int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
292    get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
293    get_box3_params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
294
295    SkBitmap inputBM;
296
297    if (!input->getROPixels(&inputBM) && inputBM.colorType() != kN32_SkColorType) {
298        return nullptr;
299    }
300
301    SkImageInfo info = SkImageInfo::Make(dstBounds.width(), dstBounds.height(),
302                                         inputBM.colorType(), inputBM.alphaType());
303
304    SkBitmap tmp, dst;
305    if (!tmp.tryAllocPixels(info) || !dst.tryAllocPixels(info)) {
306        return nullptr;
307    }
308
309    // Get ready to blur.
310    const SkPMColor* s = inputBM.getAddr32(inputBounds.x(), inputBounds.y());
311          SkPMColor* t = tmp.getAddr32(0, 0);
312          SkPMColor* d = dst.getAddr32(0, 0);
313
314    // Shift everything from being relative to the orignal input bounds to the destination bounds.
315    inputBounds.offset(-dstBounds.x(), -dstBounds.y());
316    dstBounds.offset(-dstBounds.x(), -dstBounds.y());
317
318    int w  = dstBounds.width(),
319        h  = dstBounds.height(),
320        sw = inputBM.rowBytesAsPixels();
321
322    SkIRect inputBoundsT = SkIRect::MakeLTRB(inputBounds.top(), inputBounds.left(),
323                                             inputBounds.bottom(), inputBounds.right());
324    SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width());
325
326    /**
327     *
328     * In order to make memory accesses cache-friendly, we reorder the passes to
329     * use contiguous memory reads wherever possible.
330     *
331     * For example, the 6 passes of the X-and-Y blur case are rewritten as
332     * follows. Instead of 3 passes in X and 3 passes in Y, we perform
333     * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X,
334     * then 1 pass in X transposed to Y on write.
335     *
336     * +----+       +----+       +----+        +---+       +---+       +---+        +----+
337     * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB |
338     * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+
339     *                                         +---+       +---+       +---+
340     *
341     * In this way, two of the y-blurs become x-blurs applied to transposed
342     * images, and all memory reads are contiguous.
343     */
344    if (kernelSizeX > 0 && kernelSizeY > 0) {
345        SkOpts::box_blur_xx(s, sw,  inputBounds,  t, kernelSizeX,  lowOffsetX,  highOffsetX, w, h);
346        SkOpts::box_blur_xx(t,  w,  dstBounds,    d, kernelSizeX,  highOffsetX, lowOffsetX,  w, h);
347        SkOpts::box_blur_xy(d,  w,  dstBounds,    t, kernelSizeX3, highOffsetX, highOffsetX, w, h);
348        SkOpts::box_blur_xx(t,  h,  dstBoundsT,   d, kernelSizeY,  lowOffsetY,  highOffsetY, h, w);
349        SkOpts::box_blur_xx(d,  h,  dstBoundsT,   t, kernelSizeY,  highOffsetY, lowOffsetY,  h, w);
350        SkOpts::box_blur_xy(t,  h,  dstBoundsT,   d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
351    } else if (kernelSizeX > 0) {
352        SkOpts::box_blur_xx(s, sw,  inputBounds,  d, kernelSizeX,  lowOffsetX,  highOffsetX, w, h);
353        SkOpts::box_blur_xx(d,  w,  dstBounds,    t, kernelSizeX,  highOffsetX, lowOffsetX,  w, h);
354        SkOpts::box_blur_xx(t,  w,  dstBounds,    d, kernelSizeX3, highOffsetX, highOffsetX, w, h);
355    } else if (kernelSizeY > 0) {
356        SkOpts::box_blur_yx(s, sw,  inputBoundsT, d, kernelSizeY,  lowOffsetY,  highOffsetY, h, w);
357        SkOpts::box_blur_xx(d,  h,  dstBoundsT,   t, kernelSizeY,  highOffsetY, lowOffsetY,  h, w);
358        SkOpts::box_blur_xy(t,  h,  dstBoundsT,   d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
359    }
360
361    return SkSpecialImage::MakeFromRaster(SkIRect::MakeSize(dstBounds.size()),
362                                          dst, &source->props());
363}
364
365sk_sp<SkImageFilter> SkBlurImageFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer)
366const {
367    SkASSERT(1 == this->countInputs());
368
369    auto input = xformer->apply(this->getInput(0));
370    if (this->getInput(0) != input.get()) {
371        return SkBlurImageFilter::Make(fSigma.width(), fSigma.height(), std::move(input),
372                                       this->getCropRectIfSet(), fTileMode);
373    }
374    return this->refMe();
375}
376
377SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const {
378    SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
379    bounds.outset(fSigma.width() * 3, fSigma.height() * 3);
380    return bounds;
381}
382
383SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
384                                              MapDirection) const {
385    SkVector sigma = map_sigma(fSigma, ctm);
386    return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3));
387}
388
389#ifndef SK_IGNORE_TO_STRING
390void SkBlurImageFilterImpl::toString(SkString* str) const {
391    str->appendf("SkBlurImageFilterImpl: (");
392    str->appendf("sigma: (%f, %f) tileMode: %d input (", fSigma.fWidth, fSigma.fHeight,
393                 static_cast<int>(fTileMode));
394
395    if (this->getInput(0)) {
396        this->getInput(0)->toString(str);
397    }
398
399    str->append("))");
400}
401#endif
402