• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "SkAutoPixmapStorage.h"
9 #include "SkColorPriv.h"
10 #include "SkGpuBlurUtils.h"
11 #include "SkOpts.h"
12 #include "SkReadBuffer.h"
13 #include "SkSpecialImage.h"
14 #include "SkWriteBuffer.h"
15 
16 #if SK_SUPPORT_GPU
17 #include "GrContext.h"
18 #include "GrTextureProxy.h"
19 #include "SkGr.h"
20 #endif
21 
22 class SkBlurImageFilterImpl : public SkImageFilter {
23 public:
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 
34 protected:
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 
40 private:
41     SkSize   fSigma;
42     typedef SkImageFilter INHERITED;
43 
44     friend class SkImageFilter;
45 };
46 
47 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter)
SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl)48     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl)
49 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
50 
51 ///////////////////////////////////////////////////////////////////////////////
52 
53 sk_sp<SkImageFilter> SkImageFilter::MakeBlur(SkScalar sigmaX, SkScalar sigmaY,
54                                              sk_sp<SkImageFilter> input,
55                                              const CropRect* cropRect) {
56     if (0 == sigmaX && 0 == sigmaY && !cropRect) {
57         return input;
58     }
59     return sk_sp<SkImageFilter>(new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect));
60 }
61 
62 // 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 
map_sigma(const SkSize & localSigma,const SkMatrix & ctm)69 static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) {
70     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 
SkBlurImageFilterImpl(SkScalar sigmaX,SkScalar sigmaY,sk_sp<SkImageFilter> input,const CropRect * cropRect)77 SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX,
78                                      SkScalar sigmaY,
79                                      sk_sp<SkImageFilter> input,
80                                      const CropRect* cropRect)
81     : INHERITED(&input, 1, cropRect)
82     , fSigma(SkSize::Make(sigmaX, sigmaY)) {
83 }
84 
CreateProc(SkReadBuffer & buffer)85 sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
86     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
87     SkScalar sigmaX = buffer.readScalar();
88     SkScalar sigmaY = buffer.readScalar();
89     return SkImageFilter::MakeBlur(sigmaX, sigmaY, common.getInput(0), &common.cropRect());
90 }
91 
flatten(SkWriteBuffer & buffer) const92 void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const {
93     this->INHERITED::flatten(buffer);
94     buffer.writeScalar(fSigma.fWidth);
95     buffer.writeScalar(fSigma.fHeight);
96 }
97 
get_box3_params(SkScalar s,int * kernelSize,int * kernelSize3,int * lowOffset,int * highOffset)98 static void get_box3_params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
99                             int *highOffset) {
100     float pi = SkScalarToFloat(SK_ScalarPI);
101     int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
102     *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 
onFilterImage(SkSpecialImage * source,const Context & ctx,SkIPoint * offset) const113 sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* source,
114                                                            const Context& ctx,
115                                                            SkIPoint* offset) const {
116     SkIPoint inputOffset = SkIPoint::Make(0, 0);
117 
118     sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
119     if (!input) {
120         return nullptr;
121     }
122 
123     SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY,
124                                             input->width(), input->height());
125 
126     SkIRect dstBounds;
127     if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) {
128         return nullptr;
129     }
130     if (!inputBounds.intersect(dstBounds)) {
131         return nullptr;
132     }
133 
134     const SkVector sigma = map_sigma(fSigma, ctx.ctm());
135 
136 #if SK_SUPPORT_GPU
137     if (source->isTextureBacked()) {
138         GrContext* context = source->getContext();
139 
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 
144         sk_sp<GrTextureProxy> inputTexture(input->asTextureProxyRef(context));
145         if (!inputTexture) {
146             return nullptr;
147         }
148 
149         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(),
153                                                             -inputOffset.y()));
154         }
155 
156         offset->fX = dstBounds.fLeft;
157         offset->fY = dstBounds.fTop;
158         inputBounds.offset(-inputOffset);
159         dstBounds.offset(-inputOffset);
160         // 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.
164         sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
165                                                                 context,
166                                                                 std::move(inputTexture),
167                                                                 sk_ref_sp(input->getColorSpace()),
168                                                                 dstBounds,
169                                                                 &inputBounds,
170                                                                 sigma.x(),
171                                                                 sigma.y()));
172         if (!renderTargetContext) {
173             return nullptr;
174         }
175 
176         return SkSpecialImage::MakeDeferredFromGpu(context,
177                                                    SkIRect::MakeWH(dstBounds.width(),
178                                                                    dstBounds.height()),
179                                                    kNeedNewImageUniqueID_SpecialImage,
180                                                    renderTargetContext->asTextureProxyRef(),
181                                                    renderTargetContext->refColorSpace(),
182                                                    &source->props());
183     }
184 #endif
185 
186     int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
187     int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
188     get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
189     get_box3_params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
190 
191     if (kernelSizeX < 0 || kernelSizeY < 0) {
192         return nullptr;
193     }
194 
195     if (kernelSizeX == 0 && kernelSizeY == 0) {
196         offset->fX = inputBounds.x();
197         offset->fY = inputBounds.y();
198         return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(),
199                                                         -inputOffset.y()));
200     }
201 
202     SkBitmap inputBM;
203 
204     if (!input->getROPixels(&inputBM)) {
205         return nullptr;
206     }
207 
208     if (inputBM.colorType() != kN32_SkColorType) {
209         return nullptr;
210     }
211 
212     SkImageInfo info = SkImageInfo::Make(dstBounds.width(), dstBounds.height(),
213                                          inputBM.colorType(), inputBM.alphaType());
214 
215     SkBitmap tmp, dst;
216     if (!tmp.tryAllocPixels(info) || !dst.tryAllocPixels(info)) {
217         return nullptr;
218     }
219 
220     SkAutoLockPixels inputLock(inputBM), tmpLock(tmp), dstLock(dst);
221 
222     offset->fX = dstBounds.fLeft;
223     offset->fY = dstBounds.fTop;
224     SkPMColor* t = tmp.getAddr32(0, 0);
225     SkPMColor* d = dst.getAddr32(0, 0);
226     int w = dstBounds.width(), h = dstBounds.height();
227     const SkPMColor* s = inputBM.getAddr32(inputBounds.x() - inputOffset.x(),
228                                            inputBounds.y() - inputOffset.y());
229     inputBounds.offset(-dstBounds.x(), -dstBounds.y());
230     dstBounds.offset(-dstBounds.x(), -dstBounds.y());
231     SkIRect inputBoundsT = SkIRect::MakeLTRB(inputBounds.top(), inputBounds.left(),
232                                              inputBounds.bottom(), inputBounds.right());
233     SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width());
234     int sw = int(inputBM.rowBytes() >> 2);
235 
236     /**
237      *
238      * In order to make memory accesses cache-friendly, we reorder the passes to
239      * use contiguous memory reads wherever possible.
240      *
241      * For example, the 6 passes of the X-and-Y blur case are rewritten as
242      * follows. Instead of 3 passes in X and 3 passes in Y, we perform
243      * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X,
244      * then 1 pass in X transposed to Y on write.
245      *
246      * +----+       +----+       +----+        +---+       +---+       +---+        +----+
247      * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB |
248      * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+
249      *                                         +---+       +---+       +---+
250      *
251      * In this way, two of the y-blurs become x-blurs applied to transposed
252      * images, and all memory reads are contiguous.
253      */
254     if (kernelSizeX > 0 && kernelSizeY > 0) {
255         SkOpts::box_blur_xx(s, sw,  inputBounds,  t, kernelSizeX,  lowOffsetX,  highOffsetX, w, h);
256         SkOpts::box_blur_xx(t,  w,  dstBounds,    d, kernelSizeX,  highOffsetX, lowOffsetX,  w, h);
257         SkOpts::box_blur_xy(d,  w,  dstBounds,    t, kernelSizeX3, highOffsetX, highOffsetX, w, h);
258         SkOpts::box_blur_xx(t,  h,  dstBoundsT,   d, kernelSizeY,  lowOffsetY,  highOffsetY, h, w);
259         SkOpts::box_blur_xx(d,  h,  dstBoundsT,   t, kernelSizeY,  highOffsetY, lowOffsetY,  h, w);
260         SkOpts::box_blur_xy(t,  h,  dstBoundsT,   d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
261     } else if (kernelSizeX > 0) {
262         SkOpts::box_blur_xx(s, sw,  inputBounds,  d, kernelSizeX,  lowOffsetX,  highOffsetX, w, h);
263         SkOpts::box_blur_xx(d,  w,  dstBounds,    t, kernelSizeX,  highOffsetX, lowOffsetX,  w, h);
264         SkOpts::box_blur_xx(t,  w,  dstBounds,    d, kernelSizeX3, highOffsetX, highOffsetX, w, h);
265     } else if (kernelSizeY > 0) {
266         SkOpts::box_blur_yx(s, sw,  inputBoundsT, d, kernelSizeY,  lowOffsetY,  highOffsetY, h, w);
267         SkOpts::box_blur_xx(d,  h,  dstBoundsT,   t, kernelSizeY,  highOffsetY, lowOffsetY,  h, w);
268         SkOpts::box_blur_xy(t,  h,  dstBoundsT,   d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
269     }
270 
271     return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(),
272                                                           dstBounds.height()),
273                                           dst, &source->props());
274 }
275 
276 
computeFastBounds(const SkRect & src) const277 SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const {
278     SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
279     bounds.outset(fSigma.width() * 3, fSigma.height() * 3);
280     return bounds;
281 }
282 
onFilterNodeBounds(const SkIRect & src,const SkMatrix & ctm,MapDirection) const283 SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
284                                               MapDirection) const {
285     SkVector sigma = map_sigma(fSigma, ctm);
286     return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3));
287 }
288 
289 #ifndef SK_IGNORE_TO_STRING
toString(SkString * str) const290 void SkBlurImageFilterImpl::toString(SkString* str) const {
291     str->appendf("SkBlurImageFilterImpl: (");
292     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("))");
299 }
300 #endif
301