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.
25static const int32_t gMaxKernelSize = SK_MaxS32 / sizeof(SkScalar);
26
27SkMatrixConvolutionImageFilter::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
51sk_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
79sk_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
105void 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
118SkMatrixConvolutionImageFilter::~SkMatrixConvolutionImageFilter() {
119    delete[] fKernel;
120}
121
122class UncheckedPixelFetcher {
123public:
124    static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
125        return *src.getAddr32(x, y);
126    }
127};
128
129class ClampPixelFetcher {
130public:
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
138class RepeatPixelFetcher {
139public:
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
153class ClampToBlackPixelFetcher {
154public:
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
164template<class PixelFetcher, bool convolveAlpha>
165void 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
208template<class PixelFetcher>
209void 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
220void 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
227void 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
247static 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
272static 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
287sk_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
391SkIRect 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
405bool 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
412void 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