1 | /* |
2 | * Copyright 2012 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 "include/effects/SkMatrixConvolutionImageFilter.h" |
9 | |
10 | #include "include/core/SkBitmap.h" |
11 | #include "include/core/SkRect.h" |
12 | #include "include/core/SkTileMode.h" |
13 | #include "include/core/SkUnPreMultiply.h" |
14 | #include "include/private/SkColorData.h" |
15 | #include "src/core/SkImageFilter_Base.h" |
16 | #include "src/core/SkReadBuffer.h" |
17 | #include "src/core/SkSpecialImage.h" |
18 | #include "src/core/SkWriteBuffer.h" |
19 | |
20 | #if SK_SUPPORT_GPU |
21 | #include "include/gpu/GrContext.h" |
22 | #include "src/gpu/GrTextureProxy.h" |
23 | #include "src/gpu/effects/GrMatrixConvolutionEffect.h" |
24 | #endif |
25 | |
26 | namespace { |
27 | |
28 | class SkMatrixConvolutionImageFilterImpl final : public SkImageFilter_Base { |
29 | public: |
30 | SkMatrixConvolutionImageFilterImpl(const SkISize& kernelSize, const SkScalar* kernel, |
31 | SkScalar gain, SkScalar bias, const SkIPoint& kernelOffset, |
32 | SkTileMode tileMode, bool convolveAlpha, |
33 | sk_sp<SkImageFilter> input, const CropRect* cropRect) |
34 | : INHERITED(&input, 1, cropRect) |
35 | , fKernelSize(kernelSize) |
36 | , fGain(gain) |
37 | , fBias(bias) |
38 | , fKernelOffset(kernelOffset) |
39 | , fTileMode(tileMode) |
40 | , fConvolveAlpha(convolveAlpha) { |
41 | size_t size = (size_t) sk_64_mul(fKernelSize.width(), fKernelSize.height()); |
42 | fKernel = new SkScalar[size]; |
43 | memcpy(fKernel, kernel, size * sizeof(SkScalar)); |
44 | SkASSERT(kernelSize.fWidth >= 1 && kernelSize.fHeight >= 1); |
45 | SkASSERT(kernelOffset.fX >= 0 && kernelOffset.fX < kernelSize.fWidth); |
46 | SkASSERT(kernelOffset.fY >= 0 && kernelOffset.fY < kernelSize.fHeight); |
47 | } |
48 | |
49 | ~SkMatrixConvolutionImageFilterImpl() override { |
50 | delete[] fKernel; |
51 | } |
52 | |
53 | protected: |
54 | |
55 | void flatten(SkWriteBuffer&) const override; |
56 | |
57 | sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override; |
58 | SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, |
59 | MapDirection, const SkIRect* inputRect) const override; |
60 | bool affectsTransparentBlack() const override; |
61 | |
62 | private: |
63 | friend void SkMatrixConvolutionImageFilter::RegisterFlattenables(); |
64 | SK_FLATTENABLE_HOOKS(SkMatrixConvolutionImageFilterImpl) |
65 | |
66 | SkISize fKernelSize; |
67 | SkScalar* fKernel; |
68 | SkScalar fGain; |
69 | SkScalar fBias; |
70 | SkIPoint fKernelOffset; |
71 | SkTileMode fTileMode; |
72 | bool fConvolveAlpha; |
73 | |
74 | template <class PixelFetcher, bool convolveAlpha> |
75 | void filterPixels(const SkBitmap& src, |
76 | SkBitmap* result, |
77 | SkIVector& offset, |
78 | const SkIRect& rect, |
79 | const SkIRect& bounds) const; |
80 | template <class PixelFetcher> |
81 | void filterPixels(const SkBitmap& src, |
82 | SkBitmap* result, |
83 | SkIVector& offset, |
84 | const SkIRect& rect, |
85 | const SkIRect& bounds) const; |
86 | void filterInteriorPixels(const SkBitmap& src, |
87 | SkBitmap* result, |
88 | SkIVector& offset, |
89 | const SkIRect& rect, |
90 | const SkIRect& bounds) const; |
91 | void filterBorderPixels(const SkBitmap& src, |
92 | SkBitmap* result, |
93 | SkIVector& offset, |
94 | const SkIRect& rect, |
95 | const SkIRect& bounds) const; |
96 | |
97 | typedef SkImageFilter_Base INHERITED; |
98 | }; |
99 | |
100 | class UncheckedPixelFetcher { |
101 | public: |
102 | static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { |
103 | return *src.getAddr32(x, y); |
104 | } |
105 | }; |
106 | |
107 | class ClampPixelFetcher { |
108 | public: |
109 | static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { |
110 | x = SkTPin(x, bounds.fLeft, bounds.fRight - 1); |
111 | y = SkTPin(y, bounds.fTop, bounds.fBottom - 1); |
112 | return *src.getAddr32(x, y); |
113 | } |
114 | }; |
115 | |
116 | class RepeatPixelFetcher { |
117 | public: |
118 | static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { |
119 | x = (x - bounds.left()) % bounds.width() + bounds.left(); |
120 | y = (y - bounds.top()) % bounds.height() + bounds.top(); |
121 | if (x < bounds.left()) { |
122 | x += bounds.width(); |
123 | } |
124 | if (y < bounds.top()) { |
125 | y += bounds.height(); |
126 | } |
127 | return *src.getAddr32(x, y); |
128 | } |
129 | }; |
130 | |
131 | class ClampToBlackPixelFetcher { |
132 | public: |
133 | static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { |
134 | if (x < bounds.fLeft || x >= bounds.fRight || y < bounds.fTop || y >= bounds.fBottom) { |
135 | return 0; |
136 | } else { |
137 | return *src.getAddr32(x, y); |
138 | } |
139 | } |
140 | }; |
141 | |
142 | } // end namespace |
143 | |
144 | static SkTileMode to_sktilemode(SkMatrixConvolutionImageFilter::TileMode tileMode) { |
145 | switch(tileMode) { |
146 | case SkMatrixConvolutionImageFilter::kClamp_TileMode: |
147 | return SkTileMode::kClamp; |
148 | case SkMatrixConvolutionImageFilter::kRepeat_TileMode: |
149 | return SkTileMode::kRepeat; |
150 | case SkMatrixConvolutionImageFilter::kClampToBlack_TileMode: |
151 | // Fall through |
152 | default: |
153 | return SkTileMode::kDecal; |
154 | } |
155 | } |
156 | |
157 | sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::Make(const SkISize& kernelSize, |
158 | const SkScalar* kernel, |
159 | SkScalar gain, |
160 | SkScalar bias, |
161 | const SkIPoint& kernelOffset, |
162 | TileMode tileMode, |
163 | bool convolveAlpha, |
164 | sk_sp<SkImageFilter> input, |
165 | const SkImageFilter::CropRect* cropRect) { |
166 | return Make(kernelSize, kernel, gain, bias, kernelOffset, to_sktilemode(tileMode), |
167 | convolveAlpha, std::move(input), cropRect); |
168 | } |
169 | |
170 | sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::Make(const SkISize& kernelSize, |
171 | const SkScalar* kernel, |
172 | SkScalar gain, |
173 | SkScalar bias, |
174 | const SkIPoint& kernelOffset, |
175 | SkTileMode tileMode, |
176 | bool convolveAlpha, |
177 | sk_sp<SkImageFilter> input, |
178 | const SkImageFilter::CropRect* cropRect) { |
179 | // We need to be able to read at most SK_MaxS32 bytes, so divide that |
180 | // by the size of a scalar to know how many scalars we can read. |
181 | static constexpr int32_t kMaxKernelSize = SK_MaxS32 / sizeof(SkScalar); |
182 | |
183 | if (kernelSize.width() < 1 || kernelSize.height() < 1) { |
184 | return nullptr; |
185 | } |
186 | if (kMaxKernelSize / kernelSize.fWidth < kernelSize.fHeight) { |
187 | return nullptr; |
188 | } |
189 | if (!kernel) { |
190 | return nullptr; |
191 | } |
192 | if ((kernelOffset.fX < 0) || (kernelOffset.fX >= kernelSize.fWidth) || |
193 | (kernelOffset.fY < 0) || (kernelOffset.fY >= kernelSize.fHeight)) { |
194 | return nullptr; |
195 | } |
196 | return sk_sp<SkImageFilter>(new SkMatrixConvolutionImageFilterImpl( |
197 | kernelSize, kernel, gain, bias, kernelOffset, tileMode, convolveAlpha, |
198 | std::move(input), cropRect)); |
199 | } |
200 | |
201 | void SkMatrixConvolutionImageFilter::RegisterFlattenables() { |
202 | SK_REGISTER_FLATTENABLE(SkMatrixConvolutionImageFilterImpl); |
203 | // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name |
204 | SkFlattenable::Register("SkMatrixConvolutionImageFilter" , |
205 | SkMatrixConvolutionImageFilterImpl::CreateProc); |
206 | } |
207 | |
208 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
209 | |
210 | sk_sp<SkFlattenable> SkMatrixConvolutionImageFilterImpl::CreateProc(SkReadBuffer& buffer) { |
211 | SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); |
212 | |
213 | SkISize kernelSize; |
214 | kernelSize.fWidth = buffer.readInt(); |
215 | kernelSize.fHeight = buffer.readInt(); |
216 | const int count = buffer.getArrayCount(); |
217 | |
218 | const int64_t kernelArea = sk_64_mul(kernelSize.width(), kernelSize.height()); |
219 | if (!buffer.validate(kernelArea == count)) { |
220 | return nullptr; |
221 | } |
222 | if (!buffer.validateCanReadN<SkScalar>(count)) { |
223 | return nullptr; |
224 | } |
225 | SkAutoSTArray<16, SkScalar> kernel(count); |
226 | if (!buffer.readScalarArray(kernel.get(), count)) { |
227 | return nullptr; |
228 | } |
229 | SkScalar gain = buffer.readScalar(); |
230 | SkScalar bias = buffer.readScalar(); |
231 | SkIPoint kernelOffset; |
232 | kernelOffset.fX = buffer.readInt(); |
233 | kernelOffset.fY = buffer.readInt(); |
234 | |
235 | SkTileMode tileMode; |
236 | if (buffer.isVersionLT(SkPicturePriv::kCleanupImageFilterEnums_Version)) { |
237 | tileMode = to_sktilemode(buffer.read32LE(SkMatrixConvolutionImageFilter::kLast_TileMode)); |
238 | } else { |
239 | tileMode = buffer.read32LE(SkTileMode::kLastTileMode); |
240 | } |
241 | bool convolveAlpha = buffer.readBool(); |
242 | |
243 | if (!buffer.isValid()) { |
244 | return nullptr; |
245 | } |
246 | return SkMatrixConvolutionImageFilter::Make( |
247 | kernelSize, kernel.get(), gain, bias, kernelOffset, tileMode, |
248 | convolveAlpha, common.getInput(0), &common.cropRect()); |
249 | } |
250 | |
251 | void SkMatrixConvolutionImageFilterImpl::flatten(SkWriteBuffer& buffer) const { |
252 | this->INHERITED::flatten(buffer); |
253 | buffer.writeInt(fKernelSize.fWidth); |
254 | buffer.writeInt(fKernelSize.fHeight); |
255 | buffer.writeScalarArray(fKernel, fKernelSize.fWidth * fKernelSize.fHeight); |
256 | buffer.writeScalar(fGain); |
257 | buffer.writeScalar(fBias); |
258 | buffer.writeInt(fKernelOffset.fX); |
259 | buffer.writeInt(fKernelOffset.fY); |
260 | buffer.writeInt((int) fTileMode); |
261 | buffer.writeBool(fConvolveAlpha); |
262 | } |
263 | |
264 | template<class PixelFetcher, bool convolveAlpha> |
265 | void SkMatrixConvolutionImageFilterImpl::filterPixels(const SkBitmap& src, |
266 | SkBitmap* result, |
267 | SkIVector& offset, |
268 | const SkIRect& r, |
269 | const SkIRect& bounds) const { |
270 | SkIRect rect(r); |
271 | if (!rect.intersect(bounds)) { |
272 | return; |
273 | } |
274 | for (int y = rect.fTop; y < rect.fBottom; ++y) { |
275 | SkPMColor* dptr = result->getAddr32(rect.fLeft - offset.fX, y - offset.fY); |
276 | for (int x = rect.fLeft; x < rect.fRight; ++x) { |
277 | SkScalar sumA = 0, sumR = 0, sumG = 0, sumB = 0; |
278 | for (int cy = 0; cy < fKernelSize.fHeight; cy++) { |
279 | for (int cx = 0; cx < fKernelSize.fWidth; cx++) { |
280 | SkPMColor s = PixelFetcher::fetch(src, |
281 | x + cx - fKernelOffset.fX, |
282 | y + cy - fKernelOffset.fY, |
283 | bounds); |
284 | SkScalar k = fKernel[cy * fKernelSize.fWidth + cx]; |
285 | if (convolveAlpha) { |
286 | sumA += SkGetPackedA32(s) * k; |
287 | } |
288 | sumR += SkGetPackedR32(s) * k; |
289 | sumG += SkGetPackedG32(s) * k; |
290 | sumB += SkGetPackedB32(s) * k; |
291 | } |
292 | } |
293 | int a = convolveAlpha |
294 | ? SkTPin(SkScalarFloorToInt(sumA * fGain + fBias), 0, 255) |
295 | : 255; |
296 | int r = SkTPin(SkScalarFloorToInt(sumR * fGain + fBias), 0, a); |
297 | int g = SkTPin(SkScalarFloorToInt(sumG * fGain + fBias), 0, a); |
298 | int b = SkTPin(SkScalarFloorToInt(sumB * fGain + fBias), 0, a); |
299 | if (!convolveAlpha) { |
300 | a = SkGetPackedA32(PixelFetcher::fetch(src, x, y, bounds)); |
301 | *dptr++ = SkPreMultiplyARGB(a, r, g, b); |
302 | } else { |
303 | *dptr++ = SkPackARGB32(a, r, g, b); |
304 | } |
305 | } |
306 | } |
307 | } |
308 | |
309 | template<class PixelFetcher> |
310 | void SkMatrixConvolutionImageFilterImpl::filterPixels(const SkBitmap& src, |
311 | SkBitmap* result, |
312 | SkIVector& offset, |
313 | const SkIRect& rect, |
314 | const SkIRect& bounds) const { |
315 | if (fConvolveAlpha) { |
316 | filterPixels<PixelFetcher, true>(src, result, offset, rect, bounds); |
317 | } else { |
318 | filterPixels<PixelFetcher, false>(src, result, offset, rect, bounds); |
319 | } |
320 | } |
321 | |
322 | void SkMatrixConvolutionImageFilterImpl::filterInteriorPixels(const SkBitmap& src, |
323 | SkBitmap* result, |
324 | SkIVector& offset, |
325 | const SkIRect& rect, |
326 | const SkIRect& bounds) const { |
327 | switch (fTileMode) { |
328 | case SkTileMode::kMirror: |
329 | // TODO (michaelludwig) - Implement mirror tiling, treat as repeat for now. |
330 | case SkTileMode::kRepeat: |
331 | // In repeat mode, we still need to wrap the samples around the src |
332 | filterPixels<RepeatPixelFetcher>(src, result, offset, rect, bounds); |
333 | break; |
334 | case SkTileMode::kClamp: |
335 | // Fall through |
336 | case SkTileMode::kDecal: |
337 | filterPixels<UncheckedPixelFetcher>(src, result, offset, rect, bounds); |
338 | break; |
339 | } |
340 | } |
341 | |
342 | void SkMatrixConvolutionImageFilterImpl::filterBorderPixels(const SkBitmap& src, |
343 | SkBitmap* result, |
344 | SkIVector& offset, |
345 | const SkIRect& rect, |
346 | const SkIRect& srcBounds) const { |
347 | switch (fTileMode) { |
348 | case SkTileMode::kClamp: |
349 | filterPixels<ClampPixelFetcher>(src, result, offset, rect, srcBounds); |
350 | break; |
351 | case SkTileMode::kMirror: |
352 | // TODO (michaelludwig) - Implement mirror tiling, treat as repeat for now. |
353 | case SkTileMode::kRepeat: |
354 | filterPixels<RepeatPixelFetcher>(src, result, offset, rect, srcBounds); |
355 | break; |
356 | case SkTileMode::kDecal: |
357 | filterPixels<ClampToBlackPixelFetcher>(src, result, offset, rect, srcBounds); |
358 | break; |
359 | } |
360 | } |
361 | |
362 | #if SK_SUPPORT_GPU |
363 | |
364 | static GrTextureDomain::Mode convert_tilemodes(SkTileMode tileMode) { |
365 | switch (tileMode) { |
366 | case SkTileMode::kClamp: |
367 | return GrTextureDomain::kClamp_Mode; |
368 | case SkTileMode::kMirror: |
369 | return GrTextureDomain::kMirrorRepeat_Mode; |
370 | case SkTileMode::kRepeat: |
371 | return GrTextureDomain::kRepeat_Mode; |
372 | case SkTileMode::kDecal: |
373 | return GrTextureDomain::kDecal_Mode; |
374 | default: |
375 | SkASSERT(false); |
376 | } |
377 | return GrTextureDomain::kIgnore_Mode; |
378 | } |
379 | #endif |
380 | |
381 | sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilterImpl::onFilterImage(const Context& ctx, |
382 | SkIPoint* offset) const { |
383 | SkIPoint inputOffset = SkIPoint::Make(0, 0); |
384 | sk_sp<SkSpecialImage> input(this->filterInput(0, ctx, &inputOffset)); |
385 | if (!input) { |
386 | return nullptr; |
387 | } |
388 | |
389 | SkIRect dstBounds; |
390 | input = this->applyCropRectAndPad(this->mapContext(ctx), input.get(), &inputOffset, &dstBounds); |
391 | if (!input) { |
392 | return nullptr; |
393 | } |
394 | |
395 | const SkIRect originalSrcBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY, |
396 | input->width(), input->height()); |
397 | |
398 | SkIRect srcBounds = this->onFilterNodeBounds(dstBounds, ctx.ctm(), kReverse_MapDirection, |
399 | &originalSrcBounds); |
400 | |
401 | if (SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode) { |
402 | srcBounds = DetermineRepeatedSrcBound(srcBounds, fKernelOffset, |
403 | fKernelSize, originalSrcBounds); |
404 | } else { |
405 | if (!srcBounds.intersect(dstBounds)) { |
406 | return nullptr; |
407 | } |
408 | } |
409 | |
410 | #if SK_SUPPORT_GPU |
411 | // Note: if the kernel is too big, the GPU path falls back to SW |
412 | if (ctx.gpuBacked() && |
413 | fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE) { |
414 | auto context = ctx.getContext(); |
415 | |
416 | // Ensure the input is in the destination color space. Typically applyCropRect will have |
417 | // called pad_image to account for our dilation of bounds, so the result will already be |
418 | // moved to the destination color space. If a filter DAG avoids that, then we use this |
419 | // fall-back, which saves us from having to do the xform during the filter itself. |
420 | input = ImageToColorSpace(input.get(), ctx.colorType(), ctx.colorSpace()); |
421 | |
422 | GrSurfaceProxyView inputView = input->view(context); |
423 | SkASSERT(inputView.asTextureProxy()); |
424 | |
425 | const auto isProtected = inputView.proxy()->isProtected(); |
426 | |
427 | offset->fX = dstBounds.left(); |
428 | offset->fY = dstBounds.top(); |
429 | dstBounds.offset(-inputOffset); |
430 | srcBounds.offset(-inputOffset); |
431 | // Map srcBounds from input's logical image domain to that of the proxy |
432 | srcBounds.offset(input->subset().x(), input->subset().y()); |
433 | |
434 | auto fp = GrMatrixConvolutionEffect::Make(std::move(inputView), |
435 | srcBounds, |
436 | fKernelSize, |
437 | fKernel, |
438 | fGain, |
439 | fBias, |
440 | fKernelOffset, |
441 | convert_tilemodes(fTileMode), |
442 | fConvolveAlpha); |
443 | if (!fp) { |
444 | return nullptr; |
445 | } |
446 | |
447 | // FIXME (michaelludwig) - Clean this up as part of the imagefilter refactor, some filters |
448 | // instead require a coord transform on the FP. At very least, be consistent, at best make |
449 | // it so that filter impls don't need to worry about the subset origin. |
450 | |
451 | // Must also map the dstBounds since it is used as the src rect in DrawWithFP when |
452 | // evaluating the FP, and the dst rect just uses the size of dstBounds. |
453 | dstBounds.offset(input->subset().x(), input->subset().y()); |
454 | return DrawWithFP(context, std::move(fp), dstBounds, ctx.colorType(), ctx.colorSpace(), |
455 | isProtected); |
456 | } |
457 | #endif |
458 | |
459 | SkBitmap inputBM; |
460 | if (!input->getROPixels(&inputBM)) { |
461 | return nullptr; |
462 | } |
463 | |
464 | if (inputBM.colorType() != kN32_SkColorType) { |
465 | return nullptr; |
466 | } |
467 | |
468 | if (!fConvolveAlpha && !inputBM.isOpaque()) { |
469 | // This leaves the bitmap tagged as premul, which seems weird to me, |
470 | // but is consistent with old behavior. |
471 | inputBM.readPixels(inputBM.info().makeAlphaType(kUnpremul_SkAlphaType), |
472 | inputBM.getPixels(), inputBM.rowBytes(), 0,0); |
473 | } |
474 | |
475 | if (!inputBM.getPixels()) { |
476 | return nullptr; |
477 | } |
478 | |
479 | const SkImageInfo info = SkImageInfo::MakeN32(dstBounds.width(), dstBounds.height(), |
480 | inputBM.alphaType()); |
481 | |
482 | SkBitmap dst; |
483 | if (!dst.tryAllocPixels(info)) { |
484 | return nullptr; |
485 | } |
486 | |
487 | offset->fX = dstBounds.fLeft; |
488 | offset->fY = dstBounds.fTop; |
489 | dstBounds.offset(-inputOffset); |
490 | srcBounds.offset(-inputOffset); |
491 | |
492 | SkIRect interior; |
493 | if (SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode) { |
494 | // In repeat mode, the filterPixels calls will wrap around |
495 | // so we just need to render 'dstBounds' |
496 | interior = dstBounds; |
497 | } else { |
498 | interior = SkIRect::MakeXYWH(dstBounds.left() + fKernelOffset.fX, |
499 | dstBounds.top() + fKernelOffset.fY, |
500 | dstBounds.width() - fKernelSize.fWidth + 1, |
501 | dstBounds.height() - fKernelSize.fHeight + 1); |
502 | } |
503 | |
504 | SkIRect top = SkIRect::MakeLTRB(dstBounds.left(), dstBounds.top(), |
505 | dstBounds.right(), interior.top()); |
506 | SkIRect bottom = SkIRect::MakeLTRB(dstBounds.left(), interior.bottom(), |
507 | dstBounds.right(), dstBounds.bottom()); |
508 | SkIRect left = SkIRect::MakeLTRB(dstBounds.left(), interior.top(), |
509 | interior.left(), interior.bottom()); |
510 | SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(), |
511 | dstBounds.right(), interior.bottom()); |
512 | |
513 | SkIVector dstContentOffset = { offset->fX - inputOffset.fX, offset->fY - inputOffset.fY }; |
514 | |
515 | this->filterBorderPixels(inputBM, &dst, dstContentOffset, top, srcBounds); |
516 | this->filterBorderPixels(inputBM, &dst, dstContentOffset, left, srcBounds); |
517 | this->filterInteriorPixels(inputBM, &dst, dstContentOffset, interior, srcBounds); |
518 | this->filterBorderPixels(inputBM, &dst, dstContentOffset, right, srcBounds); |
519 | this->filterBorderPixels(inputBM, &dst, dstContentOffset, bottom, srcBounds); |
520 | |
521 | return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(), dstBounds.height()), |
522 | dst); |
523 | } |
524 | |
525 | SkIRect SkMatrixConvolutionImageFilterImpl::onFilterNodeBounds( |
526 | const SkIRect& src, const SkMatrix& ctm, MapDirection dir, const SkIRect* inputRect) const { |
527 | if (kReverse_MapDirection == dir && inputRect && |
528 | (SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode)) { |
529 | SkASSERT(inputRect); |
530 | return DetermineRepeatedSrcBound(src, fKernelOffset, fKernelSize, *inputRect); |
531 | } |
532 | |
533 | SkIRect dst = src; |
534 | int w = fKernelSize.width() - 1, h = fKernelSize.height() - 1; |
535 | |
536 | if (kReverse_MapDirection == dir) { |
537 | dst.adjust(-fKernelOffset.fX, -fKernelOffset.fY, |
538 | w - fKernelOffset.fX, h - fKernelOffset.fY); |
539 | } else { |
540 | dst.adjust(fKernelOffset.fX - w, fKernelOffset.fY - h, fKernelOffset.fX, fKernelOffset.fY); |
541 | } |
542 | return dst; |
543 | } |
544 | |
545 | bool SkMatrixConvolutionImageFilterImpl::affectsTransparentBlack() const { |
546 | // It seems that the only rational way for repeat sample mode to work is if the caller |
547 | // explicitly restricts the input in which case the input range is explicitly known and |
548 | // specified. |
549 | // TODO: is seems that this should be true for clamp mode too. |
550 | |
551 | // For the other modes, because the kernel is applied in device-space, we have no idea what |
552 | // pixels it will affect in object-space. |
553 | return SkTileMode::kRepeat != fTileMode && SkTileMode::kMirror != fTileMode; |
554 | } |
555 | |