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 "SkMatrixConvolutionImageFilter.h"
9 #include "SkBitmap.h"
10 #include "SkColorPriv.h"
11 #include "SkReadBuffer.h"
12 #include "SkSpecialImage.h"
13 #include "SkWriteBuffer.h"
14 #include "SkRect.h"
15 #include "SkUnPreMultiply.h"
16
17 #if SK_SUPPORT_GPU
18 #include "GrContext.h"
19 #include "GrTextureProxy.h"
20 #include "effects/GrMatrixConvolutionEffect.h"
21 #endif
22
23 // We need to be able to read at most SK_MaxS32 bytes, so divide that
24 // by the size of a scalar to know how many scalars we can read.
25 static const int32_t gMaxKernelSize = SK_MaxS32 / sizeof(SkScalar);
26
SkMatrixConvolutionImageFilter(const SkISize & kernelSize,const SkScalar * kernel,SkScalar gain,SkScalar bias,const SkIPoint & kernelOffset,TileMode tileMode,bool convolveAlpha,sk_sp<SkImageFilter> input,const CropRect * cropRect)27 SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(const SkISize& kernelSize,
28 const SkScalar* kernel,
29 SkScalar gain,
30 SkScalar bias,
31 const SkIPoint& kernelOffset,
32 TileMode tileMode,
33 bool convolveAlpha,
34 sk_sp<SkImageFilter> input,
35 const CropRect* cropRect)
36 : INHERITED(&input, 1, cropRect)
37 , fKernelSize(kernelSize)
38 , fGain(gain)
39 , fBias(bias)
40 , fKernelOffset(kernelOffset)
41 , fTileMode(tileMode)
42 , fConvolveAlpha(convolveAlpha) {
43 size_t size = (size_t) sk_64_mul(fKernelSize.width(), fKernelSize.height());
44 fKernel = new SkScalar[size];
45 memcpy(fKernel, kernel, size * sizeof(SkScalar));
46 SkASSERT(kernelSize.fWidth >= 1 && kernelSize.fHeight >= 1);
47 SkASSERT(kernelOffset.fX >= 0 && kernelOffset.fX < kernelSize.fWidth);
48 SkASSERT(kernelOffset.fY >= 0 && kernelOffset.fY < kernelSize.fHeight);
49 }
50
Make(const SkISize & kernelSize,const SkScalar * kernel,SkScalar gain,SkScalar bias,const SkIPoint & kernelOffset,TileMode tileMode,bool convolveAlpha,sk_sp<SkImageFilter> input,const CropRect * cropRect)51 sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::Make(const SkISize& kernelSize,
52 const SkScalar* kernel,
53 SkScalar gain,
54 SkScalar bias,
55 const SkIPoint& kernelOffset,
56 TileMode tileMode,
57 bool convolveAlpha,
58 sk_sp<SkImageFilter> input,
59 const CropRect* cropRect) {
60 if (kernelSize.width() < 1 || kernelSize.height() < 1) {
61 return nullptr;
62 }
63 if (gMaxKernelSize / kernelSize.fWidth < kernelSize.fHeight) {
64 return nullptr;
65 }
66 if (!kernel) {
67 return nullptr;
68 }
69 if ((kernelOffset.fX < 0) || (kernelOffset.fX >= kernelSize.fWidth) ||
70 (kernelOffset.fY < 0) || (kernelOffset.fY >= kernelSize.fHeight)) {
71 return nullptr;
72 }
73 return sk_sp<SkImageFilter>(new SkMatrixConvolutionImageFilter(kernelSize, kernel, gain,
74 bias, kernelOffset,
75 tileMode, convolveAlpha,
76 std::move(input), cropRect));
77 }
78
CreateProc(SkReadBuffer & buffer)79 sk_sp<SkFlattenable> SkMatrixConvolutionImageFilter::CreateProc(SkReadBuffer& buffer) {
80 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
81 SkISize kernelSize;
82 kernelSize.fWidth = buffer.readInt();
83 kernelSize.fHeight = buffer.readInt();
84 const int count = buffer.getArrayCount();
85
86 const int64_t kernelArea = sk_64_mul(kernelSize.width(), kernelSize.height());
87 if (!buffer.validate(kernelArea == count)) {
88 return nullptr;
89 }
90 SkAutoSTArray<16, SkScalar> kernel(count);
91 if (!buffer.readScalarArray(kernel.get(), count)) {
92 return nullptr;
93 }
94 SkScalar gain = buffer.readScalar();
95 SkScalar bias = buffer.readScalar();
96 SkIPoint kernelOffset;
97 kernelOffset.fX = buffer.readInt();
98 kernelOffset.fY = buffer.readInt();
99 TileMode tileMode = (TileMode)buffer.readInt();
100 bool convolveAlpha = buffer.readBool();
101 return Make(kernelSize, kernel.get(), gain, bias, kernelOffset, tileMode,
102 convolveAlpha, common.getInput(0), &common.cropRect());
103 }
104
flatten(SkWriteBuffer & buffer) const105 void SkMatrixConvolutionImageFilter::flatten(SkWriteBuffer& buffer) const {
106 this->INHERITED::flatten(buffer);
107 buffer.writeInt(fKernelSize.fWidth);
108 buffer.writeInt(fKernelSize.fHeight);
109 buffer.writeScalarArray(fKernel, fKernelSize.fWidth * fKernelSize.fHeight);
110 buffer.writeScalar(fGain);
111 buffer.writeScalar(fBias);
112 buffer.writeInt(fKernelOffset.fX);
113 buffer.writeInt(fKernelOffset.fY);
114 buffer.writeInt((int) fTileMode);
115 buffer.writeBool(fConvolveAlpha);
116 }
117
~SkMatrixConvolutionImageFilter()118 SkMatrixConvolutionImageFilter::~SkMatrixConvolutionImageFilter() {
119 delete[] fKernel;
120 }
121
122 class UncheckedPixelFetcher {
123 public:
fetch(const SkBitmap & src,int x,int y,const SkIRect & bounds)124 static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
125 return *src.getAddr32(x, y);
126 }
127 };
128
129 class ClampPixelFetcher {
130 public:
fetch(const SkBitmap & src,int x,int y,const SkIRect & bounds)131 static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
132 x = SkTPin(x, bounds.fLeft, bounds.fRight - 1);
133 y = SkTPin(y, bounds.fTop, bounds.fBottom - 1);
134 return *src.getAddr32(x, y);
135 }
136 };
137
138 class RepeatPixelFetcher {
139 public:
fetch(const SkBitmap & src,int x,int y,const SkIRect & bounds)140 static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
141 x = (x - bounds.left()) % bounds.width() + bounds.left();
142 y = (y - bounds.top()) % bounds.height() + bounds.top();
143 if (x < bounds.left()) {
144 x += bounds.width();
145 }
146 if (y < bounds.top()) {
147 y += bounds.height();
148 }
149 return *src.getAddr32(x, y);
150 }
151 };
152
153 class ClampToBlackPixelFetcher {
154 public:
fetch(const SkBitmap & src,int x,int y,const SkIRect & bounds)155 static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
156 if (x < bounds.fLeft || x >= bounds.fRight || y < bounds.fTop || y >= bounds.fBottom) {
157 return 0;
158 } else {
159 return *src.getAddr32(x, y);
160 }
161 }
162 };
163
164 template<class PixelFetcher, bool convolveAlpha>
filterPixels(const SkBitmap & src,SkBitmap * result,const SkIRect & r,const SkIRect & bounds) const165 void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src,
166 SkBitmap* result,
167 const SkIRect& r,
168 const SkIRect& bounds) const {
169 SkIRect rect(r);
170 if (!rect.intersect(bounds)) {
171 return;
172 }
173 for (int y = rect.fTop; y < rect.fBottom; ++y) {
174 SkPMColor* dptr = result->getAddr32(rect.fLeft - bounds.fLeft, y - bounds.fTop);
175 for (int x = rect.fLeft; x < rect.fRight; ++x) {
176 SkScalar sumA = 0, sumR = 0, sumG = 0, sumB = 0;
177 for (int cy = 0; cy < fKernelSize.fHeight; cy++) {
178 for (int cx = 0; cx < fKernelSize.fWidth; cx++) {
179 SkPMColor s = PixelFetcher::fetch(src,
180 x + cx - fKernelOffset.fX,
181 y + cy - fKernelOffset.fY,
182 bounds);
183 SkScalar k = fKernel[cy * fKernelSize.fWidth + cx];
184 if (convolveAlpha) {
185 sumA += SkGetPackedA32(s) * k;
186 }
187 sumR += SkGetPackedR32(s) * k;
188 sumG += SkGetPackedG32(s) * k;
189 sumB += SkGetPackedB32(s) * k;
190 }
191 }
192 int a = convolveAlpha
193 ? SkClampMax(SkScalarFloorToInt(sumA * fGain + fBias), 255)
194 : 255;
195 int r = SkClampMax(SkScalarFloorToInt(sumR * fGain + fBias), a);
196 int g = SkClampMax(SkScalarFloorToInt(sumG * fGain + fBias), a);
197 int b = SkClampMax(SkScalarFloorToInt(sumB * fGain + fBias), a);
198 if (!convolveAlpha) {
199 a = SkGetPackedA32(PixelFetcher::fetch(src, x, y, bounds));
200 *dptr++ = SkPreMultiplyARGB(a, r, g, b);
201 } else {
202 *dptr++ = SkPackARGB32(a, r, g, b);
203 }
204 }
205 }
206 }
207
208 template<class PixelFetcher>
filterPixels(const SkBitmap & src,SkBitmap * result,const SkIRect & rect,const SkIRect & bounds) const209 void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src,
210 SkBitmap* result,
211 const SkIRect& rect,
212 const SkIRect& bounds) const {
213 if (fConvolveAlpha) {
214 filterPixels<PixelFetcher, true>(src, result, rect, bounds);
215 } else {
216 filterPixels<PixelFetcher, false>(src, result, rect, bounds);
217 }
218 }
219
filterInteriorPixels(const SkBitmap & src,SkBitmap * result,const SkIRect & rect,const SkIRect & bounds) const220 void SkMatrixConvolutionImageFilter::filterInteriorPixels(const SkBitmap& src,
221 SkBitmap* result,
222 const SkIRect& rect,
223 const SkIRect& bounds) const {
224 filterPixels<UncheckedPixelFetcher>(src, result, rect, bounds);
225 }
226
filterBorderPixels(const SkBitmap & src,SkBitmap * result,const SkIRect & rect,const SkIRect & bounds) const227 void SkMatrixConvolutionImageFilter::filterBorderPixels(const SkBitmap& src,
228 SkBitmap* result,
229 const SkIRect& rect,
230 const SkIRect& bounds) const {
231 switch (fTileMode) {
232 case kClamp_TileMode:
233 filterPixels<ClampPixelFetcher>(src, result, rect, bounds);
234 break;
235 case kRepeat_TileMode:
236 filterPixels<RepeatPixelFetcher>(src, result, rect, bounds);
237 break;
238 case kClampToBlack_TileMode:
239 filterPixels<ClampToBlackPixelFetcher>(src, result, rect, bounds);
240 break;
241 }
242 }
243
244 // FIXME: This should be refactored to SkImageFilterUtils for
245 // use by other filters. For now, we assume the input is always
246 // premultiplied and unpremultiply it
unpremultiply_bitmap(const SkBitmap & src)247 static SkBitmap unpremultiply_bitmap(const SkBitmap& src)
248 {
249 SkAutoLockPixels alp(src);
250 if (!src.getPixels()) {
251 return SkBitmap();
252 }
253
254 const SkImageInfo info = SkImageInfo::MakeN32(src.width(), src.height(), src.alphaType());
255 SkBitmap result;
256 if (!result.tryAllocPixels(info)) {
257 return SkBitmap();
258 }
259 SkAutoLockPixels resultLock(result);
260 for (int y = 0; y < src.height(); ++y) {
261 const uint32_t* srcRow = src.getAddr32(0, y);
262 uint32_t* dstRow = result.getAddr32(0, y);
263 for (int x = 0; x < src.width(); ++x) {
264 dstRow[x] = SkUnPreMultiply::PMColorToColor(srcRow[x]);
265 }
266 }
267 return result;
268 }
269
270 #if SK_SUPPORT_GPU
271
convert_tilemodes(SkMatrixConvolutionImageFilter::TileMode tileMode)272 static GrTextureDomain::Mode convert_tilemodes(SkMatrixConvolutionImageFilter::TileMode tileMode) {
273 switch (tileMode) {
274 case SkMatrixConvolutionImageFilter::kClamp_TileMode:
275 return GrTextureDomain::kClamp_Mode;
276 case SkMatrixConvolutionImageFilter::kRepeat_TileMode:
277 return GrTextureDomain::kRepeat_Mode;
278 case SkMatrixConvolutionImageFilter::kClampToBlack_TileMode:
279 return GrTextureDomain::kDecal_Mode;
280 default:
281 SkASSERT(false);
282 }
283 return GrTextureDomain::kIgnore_Mode;
284 }
285 #endif
286
onFilterImage(SkSpecialImage * source,const Context & ctx,SkIPoint * offset) const287 sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialImage* source,
288 const Context& ctx,
289 SkIPoint* offset) const {
290 SkIPoint inputOffset = SkIPoint::Make(0, 0);
291 sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
292 if (!input) {
293 return nullptr;
294 }
295
296 SkIRect bounds;
297 input = this->applyCropRect(this->mapContext(ctx), input.get(), &inputOffset, &bounds);
298 if (!input) {
299 return nullptr;
300 }
301
302 #if SK_SUPPORT_GPU
303 // Note: if the kernel is too big, the GPU path falls back to SW
304 if (source->isTextureBacked() &&
305 fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE) {
306 GrContext* context = source->getContext();
307
308 // Ensure the input is in the destination color space. Typically applyCropRect will have
309 // called pad_image to account for our dilation of bounds, so the result will already be
310 // moved to the destination color space. If a filter DAG avoids that, then we use this
311 // fall-back, which saves us from having to do the xform during the filter itself.
312 input = ImageToColorSpace(input.get(), ctx.outputProperties());
313
314 sk_sp<GrTextureProxy> inputProxy(input->asTextureProxyRef(context));
315 SkASSERT(inputProxy);
316
317 offset->fX = bounds.left();
318 offset->fY = bounds.top();
319 bounds.offset(-inputOffset);
320
321 sk_sp<GrFragmentProcessor> fp(GrMatrixConvolutionEffect::Make(context->resourceProvider(),
322 std::move(inputProxy),
323 bounds,
324 fKernelSize,
325 fKernel,
326 fGain,
327 fBias,
328 fKernelOffset,
329 convert_tilemodes(fTileMode),
330 fConvolveAlpha));
331 if (!fp) {
332 return nullptr;
333 }
334
335 return DrawWithFP(context, std::move(fp), bounds, ctx.outputProperties());
336 }
337 #endif
338
339 SkBitmap inputBM;
340
341 if (!input->getROPixels(&inputBM)) {
342 return nullptr;
343 }
344
345 if (inputBM.colorType() != kN32_SkColorType) {
346 return nullptr;
347 }
348
349 if (!fConvolveAlpha && !inputBM.isOpaque()) {
350 inputBM = unpremultiply_bitmap(inputBM);
351 }
352
353 SkAutoLockPixels alp(inputBM);
354 if (!inputBM.getPixels()) {
355 return nullptr;
356 }
357
358 const SkImageInfo info = SkImageInfo::MakeN32(bounds.width(), bounds.height(),
359 inputBM.alphaType());
360
361 SkBitmap dst;
362 if (!dst.tryAllocPixels(info)) {
363 return nullptr;
364 }
365
366 SkAutoLockPixels dstLock(dst);
367
368 offset->fX = bounds.fLeft;
369 offset->fY = bounds.fTop;
370 bounds.offset(-inputOffset);
371 SkIRect interior = SkIRect::MakeXYWH(bounds.left() + fKernelOffset.fX,
372 bounds.top() + fKernelOffset.fY,
373 bounds.width() - fKernelSize.fWidth + 1,
374 bounds.height() - fKernelSize.fHeight + 1);
375 SkIRect top = SkIRect::MakeLTRB(bounds.left(), bounds.top(), bounds.right(), interior.top());
376 SkIRect bottom = SkIRect::MakeLTRB(bounds.left(), interior.bottom(),
377 bounds.right(), bounds.bottom());
378 SkIRect left = SkIRect::MakeLTRB(bounds.left(), interior.top(),
379 interior.left(), interior.bottom());
380 SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(),
381 bounds.right(), interior.bottom());
382 this->filterBorderPixels(inputBM, &dst, top, bounds);
383 this->filterBorderPixels(inputBM, &dst, left, bounds);
384 this->filterInteriorPixels(inputBM, &dst, interior, bounds);
385 this->filterBorderPixels(inputBM, &dst, right, bounds);
386 this->filterBorderPixels(inputBM, &dst, bottom, bounds);
387 return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()),
388 dst);
389 }
390
onFilterNodeBounds(const SkIRect & src,const SkMatrix & ctm,MapDirection direction) const391 SkIRect SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
392 MapDirection direction) const {
393 SkIRect dst = src;
394 int w = fKernelSize.width() - 1, h = fKernelSize.height() - 1;
395 dst.fRight += w;
396 dst.fBottom += h;
397 if (kReverse_MapDirection == direction) {
398 dst.offset(-fKernelOffset);
399 } else {
400 dst.offset(fKernelOffset - SkIPoint::Make(w, h));
401 }
402 return dst;
403 }
404
affectsTransparentBlack() const405 bool SkMatrixConvolutionImageFilter::affectsTransparentBlack() const {
406 // Because the kernel is applied in device-space, we have no idea what
407 // pixels it will affect in object-space.
408 return true;
409 }
410
411 #ifndef SK_IGNORE_TO_STRING
toString(SkString * str) const412 void SkMatrixConvolutionImageFilter::toString(SkString* str) const {
413 str->appendf("SkMatrixConvolutionImageFilter: (");
414 str->appendf("size: (%d,%d) kernel: (", fKernelSize.width(), fKernelSize.height());
415 for (int y = 0; y < fKernelSize.height(); y++) {
416 for (int x = 0; x < fKernelSize.width(); x++) {
417 str->appendf("%f ", fKernel[y * fKernelSize.width() + x]);
418 }
419 }
420 str->appendf(")");
421 str->appendf("gain: %f bias: %f ", fGain, fBias);
422 str->appendf("offset: (%d, %d) ", fKernelOffset.fX, fKernelOffset.fY);
423 str->appendf("convolveAlpha: %s", fConvolveAlpha ? "true" : "false");
424 str->append(")");
425 }
426 #endif
427