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