Line data Source code
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 0 : 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 0 : 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 : sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
39 : SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override;
40 :
41 : private:
42 : SkSize fSigma;
43 : typedef SkImageFilter INHERITED;
44 :
45 : friend class SkImageFilter;
46 : };
47 :
48 0 : SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter)
49 0 : SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl)
50 0 : SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
51 :
52 : ///////////////////////////////////////////////////////////////////////////////
53 :
54 0 : sk_sp<SkImageFilter> SkImageFilter::MakeBlur(SkScalar sigmaX, SkScalar sigmaY,
55 : sk_sp<SkImageFilter> input,
56 : const CropRect* cropRect) {
57 0 : if (0 == sigmaX && 0 == sigmaY && !cropRect) {
58 0 : return input;
59 : }
60 0 : return sk_sp<SkImageFilter>(new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect));
61 : }
62 :
63 : // This rather arbitrary-looking value results in a maximum box blur kernel size
64 : // of 1000 pixels on the raster path, which matches the WebKit and Firefox
65 : // implementations. Since the GPU path does not compute a box blur, putting
66 : // the limit on sigma ensures consistent behaviour between the GPU and
67 : // raster paths.
68 : #define MAX_SIGMA SkIntToScalar(532)
69 :
70 0 : static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) {
71 0 : SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height());
72 0 : ctm.mapVectors(&sigma, 1);
73 0 : sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA);
74 0 : sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA);
75 0 : return sigma;
76 : }
77 :
78 0 : SkBlurImageFilterImpl::SkBlurImageFilterImpl(
79 0 : SkScalar sigmaX, SkScalar sigmaY, sk_sp<SkImageFilter> input, const CropRect* cropRect)
80 0 : : INHERITED(&input, 1, cropRect), fSigma{sigmaX, sigmaY} {}
81 :
82 0 : sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
83 0 : SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
84 0 : SkScalar sigmaX = buffer.readScalar();
85 0 : SkScalar sigmaY = buffer.readScalar();
86 0 : return SkImageFilter::MakeBlur(sigmaX, sigmaY, common.getInput(0), &common.cropRect());
87 : }
88 :
89 0 : void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const {
90 0 : this->INHERITED::flatten(buffer);
91 0 : buffer.writeScalar(fSigma.fWidth);
92 0 : buffer.writeScalar(fSigma.fHeight);
93 0 : }
94 :
95 0 : static void get_box3_params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
96 : int *highOffset) {
97 0 : float pi = SkScalarToFloat(SK_ScalarPI);
98 0 : int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
99 0 : *kernelSize = d;
100 0 : if (d % 2 == 1) {
101 0 : *lowOffset = *highOffset = (d - 1) / 2;
102 0 : *kernelSize3 = d;
103 : } else {
104 0 : *highOffset = d / 2;
105 0 : *lowOffset = *highOffset - 1;
106 0 : *kernelSize3 = d + 1;
107 : }
108 0 : }
109 :
110 0 : sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* source,
111 : const Context& ctx,
112 : SkIPoint* offset) const {
113 0 : SkIPoint inputOffset = SkIPoint::Make(0, 0);
114 :
115 0 : sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
116 0 : if (!input) {
117 0 : return nullptr;
118 : }
119 :
120 : SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY,
121 0 : input->width(), input->height());
122 :
123 : SkIRect dstBounds;
124 0 : if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) {
125 0 : return nullptr;
126 : }
127 0 : if (!inputBounds.intersect(dstBounds)) {
128 0 : return nullptr;
129 : }
130 :
131 0 : const SkVector sigma = map_sigma(fSigma, ctx.ctm());
132 :
133 : #if SK_SUPPORT_GPU
134 0 : if (source->isTextureBacked()) {
135 0 : GrContext* context = source->getContext();
136 :
137 : // Ensure the input is in the destination's gamut. This saves us from having to do the
138 : // xform during the filter itself.
139 0 : input = ImageToColorSpace(input.get(), ctx.outputProperties());
140 :
141 0 : sk_sp<GrTextureProxy> inputTexture(input->asTextureProxyRef(context));
142 0 : if (!inputTexture) {
143 0 : return nullptr;
144 : }
145 :
146 0 : if (0 == sigma.x() && 0 == sigma.y()) {
147 0 : offset->fX = inputBounds.x();
148 0 : offset->fY = inputBounds.y();
149 0 : return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(),
150 0 : -inputOffset.y()));
151 : }
152 :
153 0 : offset->fX = dstBounds.fLeft;
154 0 : offset->fY = dstBounds.fTop;
155 0 : inputBounds.offset(-inputOffset);
156 0 : dstBounds.offset(-inputOffset);
157 : // Typically, we would create the RTC with the output's color space (from ctx), but we
158 : // always blur in the PixelConfig of the *input*. Those might not be compatible (if they
159 : // have different transfer functions). We've already guaranteed that those color spaces
160 : // have the same gamut, so in this case, we do everything in the input's color space.
161 : sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
162 : context,
163 0 : std::move(inputTexture),
164 0 : sk_ref_sp(input->getColorSpace()),
165 : dstBounds,
166 : &inputBounds,
167 : sigma.x(),
168 0 : sigma.y()));
169 0 : if (!renderTargetContext) {
170 0 : return nullptr;
171 : }
172 :
173 : return SkSpecialImage::MakeDeferredFromGpu(context,
174 0 : SkIRect::MakeWH(dstBounds.width(),
175 0 : dstBounds.height()),
176 : kNeedNewImageUniqueID_SpecialImage,
177 0 : renderTargetContext->asTextureProxyRef(),
178 0 : renderTargetContext->refColorSpace(),
179 0 : &source->props());
180 : }
181 : #endif
182 :
183 : int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
184 : int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
185 0 : get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
186 0 : get_box3_params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
187 :
188 0 : if (kernelSizeX < 0 || kernelSizeY < 0) {
189 0 : return nullptr;
190 : }
191 :
192 0 : if (kernelSizeX == 0 && kernelSizeY == 0) {
193 0 : offset->fX = inputBounds.x();
194 0 : offset->fY = inputBounds.y();
195 0 : return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(),
196 0 : -inputOffset.y()));
197 : }
198 :
199 0 : SkBitmap inputBM;
200 :
201 0 : if (!input->getROPixels(&inputBM)) {
202 0 : return nullptr;
203 : }
204 :
205 0 : if (inputBM.colorType() != kN32_SkColorType) {
206 0 : return nullptr;
207 : }
208 :
209 : SkImageInfo info = SkImageInfo::Make(dstBounds.width(), dstBounds.height(),
210 0 : inputBM.colorType(), inputBM.alphaType());
211 :
212 0 : SkBitmap tmp, dst;
213 0 : if (!tmp.tryAllocPixels(info) || !dst.tryAllocPixels(info)) {
214 0 : return nullptr;
215 : }
216 :
217 0 : SkAutoLockPixels inputLock(inputBM), tmpLock(tmp), dstLock(dst);
218 :
219 0 : offset->fX = dstBounds.fLeft;
220 0 : offset->fY = dstBounds.fTop;
221 0 : SkPMColor* t = tmp.getAddr32(0, 0);
222 0 : SkPMColor* d = dst.getAddr32(0, 0);
223 0 : int w = dstBounds.width(), h = dstBounds.height();
224 0 : const SkPMColor* s = inputBM.getAddr32(inputBounds.x() - inputOffset.x(),
225 0 : inputBounds.y() - inputOffset.y());
226 0 : inputBounds.offset(-dstBounds.x(), -dstBounds.y());
227 0 : dstBounds.offset(-dstBounds.x(), -dstBounds.y());
228 : SkIRect inputBoundsT = SkIRect::MakeLTRB(inputBounds.top(), inputBounds.left(),
229 0 : inputBounds.bottom(), inputBounds.right());
230 0 : SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width());
231 0 : int sw = int(inputBM.rowBytes() >> 2);
232 :
233 : /**
234 : *
235 : * In order to make memory accesses cache-friendly, we reorder the passes to
236 : * use contiguous memory reads wherever possible.
237 : *
238 : * For example, the 6 passes of the X-and-Y blur case are rewritten as
239 : * follows. Instead of 3 passes in X and 3 passes in Y, we perform
240 : * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X,
241 : * then 1 pass in X transposed to Y on write.
242 : *
243 : * +----+ +----+ +----+ +---+ +---+ +---+ +----+
244 : * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB |
245 : * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+
246 : * +---+ +---+ +---+
247 : *
248 : * In this way, two of the y-blurs become x-blurs applied to transposed
249 : * images, and all memory reads are contiguous.
250 : */
251 0 : if (kernelSizeX > 0 && kernelSizeY > 0) {
252 0 : SkOpts::box_blur_xx(s, sw, inputBounds, t, kernelSizeX, lowOffsetX, highOffsetX, w, h);
253 0 : SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX, highOffsetX, lowOffsetX, w, h);
254 0 : SkOpts::box_blur_xy(d, w, dstBounds, t, kernelSizeX3, highOffsetX, highOffsetX, w, h);
255 0 : SkOpts::box_blur_xx(t, h, dstBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
256 0 : SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
257 0 : SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
258 0 : } else if (kernelSizeX > 0) {
259 0 : SkOpts::box_blur_xx(s, sw, inputBounds, d, kernelSizeX, lowOffsetX, highOffsetX, w, h);
260 0 : SkOpts::box_blur_xx(d, w, dstBounds, t, kernelSizeX, highOffsetX, lowOffsetX, w, h);
261 0 : SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX3, highOffsetX, highOffsetX, w, h);
262 0 : } else if (kernelSizeY > 0) {
263 0 : SkOpts::box_blur_yx(s, sw, inputBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
264 0 : SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
265 0 : SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
266 : }
267 :
268 0 : return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(),
269 0 : dstBounds.height()),
270 0 : dst, &source->props());
271 : }
272 :
273 0 : sk_sp<SkImageFilter> SkBlurImageFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer)
274 : const {
275 0 : SkASSERT(1 == this->countInputs());
276 0 : if (!this->getInput(0)) {
277 0 : return sk_ref_sp(const_cast<SkBlurImageFilterImpl*>(this));
278 : }
279 :
280 0 : sk_sp<SkImageFilter> input = this->getInput(0)->makeColorSpace(xformer);
281 0 : return SkImageFilter::MakeBlur(fSigma.width(), fSigma.height(), std::move(input),
282 0 : this->getCropRectIfSet());
283 : }
284 :
285 0 : SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const {
286 0 : SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
287 0 : bounds.outset(fSigma.width() * 3, fSigma.height() * 3);
288 0 : return bounds;
289 : }
290 :
291 0 : SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
292 : MapDirection) const {
293 0 : SkVector sigma = map_sigma(fSigma, ctm);
294 0 : return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3));
295 : }
296 :
297 : #ifndef SK_IGNORE_TO_STRING
298 0 : void SkBlurImageFilterImpl::toString(SkString* str) const {
299 0 : str->appendf("SkBlurImageFilterImpl: (");
300 0 : str->appendf("sigma: (%f, %f) input (", fSigma.fWidth, fSigma.fHeight);
301 :
302 0 : if (this->getInput(0)) {
303 0 : this->getInput(0)->toString(str);
304 : }
305 :
306 0 : str->append("))");
307 0 : }
308 : #endif
|