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