aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp')
-rw-r--r--src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp482
1 files changed, 482 insertions, 0 deletions
diff --git a/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp b/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
new file mode 100644
index 0000000000..9e71ab629e
--- /dev/null
+++ b/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
@@ -0,0 +1,482 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMatrixConvolutionImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorData.h"
+#include "SkColorSpaceXformer.h"
+#include "SkFlattenablePriv.h"
+#include "SkImageFilterPriv.h"
+#include "SkReadBuffer.h"
+#include "SkSpecialImage.h"
+#include "SkWriteBuffer.h"
+#include "SkRect.h"
+#include "SkUnPreMultiply.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrTextureProxy.h"
+#include "effects/GrMatrixConvolutionEffect.h"
+#endif
+
+// We need to be able to read at most SK_MaxS32 bytes, so divide that
+// by the size of a scalar to know how many scalars we can read.
+static const int32_t gMaxKernelSize = SK_MaxS32 / sizeof(SkScalar);
+
+SkMatrixConvolutionImageFilter::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)
+ : INHERITED(&input, 1, cropRect)
+ , fKernelSize(kernelSize)
+ , fGain(gain)
+ , fBias(bias)
+ , fKernelOffset(kernelOffset)
+ , fTileMode(tileMode)
+ , fConvolveAlpha(convolveAlpha) {
+ size_t size = (size_t) sk_64_mul(fKernelSize.width(), fKernelSize.height());
+ fKernel = new SkScalar[size];
+ memcpy(fKernel, kernel, size * sizeof(SkScalar));
+ SkASSERT(kernelSize.fWidth >= 1 && kernelSize.fHeight >= 1);
+ SkASSERT(kernelOffset.fX >= 0 && kernelOffset.fX < kernelSize.fWidth);
+ SkASSERT(kernelOffset.fY >= 0 && kernelOffset.fY < kernelSize.fHeight);
+}
+
+sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::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) {
+ if (kernelSize.width() < 1 || kernelSize.height() < 1) {
+ return nullptr;
+ }
+ if (gMaxKernelSize / kernelSize.fWidth < kernelSize.fHeight) {
+ return nullptr;
+ }
+ if (!kernel) {
+ return nullptr;
+ }
+ if ((kernelOffset.fX < 0) || (kernelOffset.fX >= kernelSize.fWidth) ||
+ (kernelOffset.fY < 0) || (kernelOffset.fY >= kernelSize.fHeight)) {
+ return nullptr;
+ }
+ return sk_sp<SkImageFilter>(new SkMatrixConvolutionImageFilter(kernelSize, kernel, gain,
+ bias, kernelOffset,
+ tileMode, convolveAlpha,
+ std::move(input), cropRect));
+}
+
+sk_sp<SkFlattenable> SkMatrixConvolutionImageFilter::CreateProc(SkReadBuffer& buffer) {
+ SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
+
+ SkISize kernelSize;
+ kernelSize.fWidth = buffer.readInt();
+ kernelSize.fHeight = buffer.readInt();
+ const int count = buffer.getArrayCount();
+
+ const int64_t kernelArea = sk_64_mul(kernelSize.width(), kernelSize.height());
+ if (!buffer.validate(kernelArea == count)) {
+ return nullptr;
+ }
+ if (!buffer.validateCanReadN<SkScalar>(count)) {
+ return nullptr;
+ }
+ SkAutoSTArray<16, SkScalar> kernel(count);
+ if (!buffer.readScalarArray(kernel.get(), count)) {
+ return nullptr;
+ }
+ SkScalar gain = buffer.readScalar();
+ SkScalar bias = buffer.readScalar();
+ SkIPoint kernelOffset;
+ kernelOffset.fX = buffer.readInt();
+ kernelOffset.fY = buffer.readInt();
+
+ TileMode tileMode = buffer.read32LE(kLast_TileMode);
+ bool convolveAlpha = buffer.readBool();
+
+ if (!buffer.isValid()) {
+ return nullptr;
+ }
+ return Make(kernelSize, kernel.get(), gain, bias, kernelOffset, tileMode,
+ convolveAlpha, common.getInput(0), &common.cropRect());
+}
+
+void SkMatrixConvolutionImageFilter::flatten(SkWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt(fKernelSize.fWidth);
+ buffer.writeInt(fKernelSize.fHeight);
+ buffer.writeScalarArray(fKernel, fKernelSize.fWidth * fKernelSize.fHeight);
+ buffer.writeScalar(fGain);
+ buffer.writeScalar(fBias);
+ buffer.writeInt(fKernelOffset.fX);
+ buffer.writeInt(fKernelOffset.fY);
+ buffer.writeInt((int) fTileMode);
+ buffer.writeBool(fConvolveAlpha);
+}
+
+SkMatrixConvolutionImageFilter::~SkMatrixConvolutionImageFilter() {
+ delete[] fKernel;
+}
+
+class UncheckedPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
+ return *src.getAddr32(x, y);
+ }
+};
+
+class ClampPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
+ x = SkTPin(x, bounds.fLeft, bounds.fRight - 1);
+ y = SkTPin(y, bounds.fTop, bounds.fBottom - 1);
+ return *src.getAddr32(x, y);
+ }
+};
+
+class RepeatPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
+ x = (x - bounds.left()) % bounds.width() + bounds.left();
+ y = (y - bounds.top()) % bounds.height() + bounds.top();
+ if (x < bounds.left()) {
+ x += bounds.width();
+ }
+ if (y < bounds.top()) {
+ y += bounds.height();
+ }
+ return *src.getAddr32(x, y);
+ }
+};
+
+class ClampToBlackPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) {
+ if (x < bounds.fLeft || x >= bounds.fRight || y < bounds.fTop || y >= bounds.fBottom) {
+ return 0;
+ } else {
+ return *src.getAddr32(x, y);
+ }
+ }
+};
+
+template<class PixelFetcher, bool convolveAlpha>
+void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src,
+ SkBitmap* result,
+ SkIVector& offset,
+ const SkIRect& r,
+ const SkIRect& bounds) const {
+ SkIRect rect(r);
+ if (!rect.intersect(bounds)) {
+ return;
+ }
+ for (int y = rect.fTop; y < rect.fBottom; ++y) {
+ SkPMColor* dptr = result->getAddr32(rect.fLeft - offset.fX, y - offset.fY);
+ for (int x = rect.fLeft; x < rect.fRight; ++x) {
+ SkScalar sumA = 0, sumR = 0, sumG = 0, sumB = 0;
+ for (int cy = 0; cy < fKernelSize.fHeight; cy++) {
+ for (int cx = 0; cx < fKernelSize.fWidth; cx++) {
+ SkPMColor s = PixelFetcher::fetch(src,
+ x + cx - fKernelOffset.fX,
+ y + cy - fKernelOffset.fY,
+ bounds);
+ SkScalar k = fKernel[cy * fKernelSize.fWidth + cx];
+ if (convolveAlpha) {
+ sumA += SkGetPackedA32(s) * k;
+ }
+ sumR += SkGetPackedR32(s) * k;
+ sumG += SkGetPackedG32(s) * k;
+ sumB += SkGetPackedB32(s) * k;
+ }
+ }
+ int a = convolveAlpha
+ ? SkClampMax(SkScalarFloorToInt(sumA * fGain + fBias), 255)
+ : 255;
+ int r = SkClampMax(SkScalarFloorToInt(sumR * fGain + fBias), a);
+ int g = SkClampMax(SkScalarFloorToInt(sumG * fGain + fBias), a);
+ int b = SkClampMax(SkScalarFloorToInt(sumB * fGain + fBias), a);
+ if (!convolveAlpha) {
+ a = SkGetPackedA32(PixelFetcher::fetch(src, x, y, bounds));
+ *dptr++ = SkPreMultiplyARGB(a, r, g, b);
+ } else {
+ *dptr++ = SkPackARGB32(a, r, g, b);
+ }
+ }
+ }
+}
+
+template<class PixelFetcher>
+void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src,
+ SkBitmap* result,
+ SkIVector& offset,
+ const SkIRect& rect,
+ const SkIRect& bounds) const {
+ if (fConvolveAlpha) {
+ filterPixels<PixelFetcher, true>(src, result, offset, rect, bounds);
+ } else {
+ filterPixels<PixelFetcher, false>(src, result, offset, rect, bounds);
+ }
+}
+
+void SkMatrixConvolutionImageFilter::filterInteriorPixels(const SkBitmap& src,
+ SkBitmap* result,
+ SkIVector& offset,
+ const SkIRect& rect,
+ const SkIRect& bounds) const {
+ switch (fTileMode) {
+ case kRepeat_TileMode:
+ // In repeat mode, we still need to wrap the samples around the src
+ filterPixels<RepeatPixelFetcher>(src, result, offset, rect, bounds);
+ break;
+ case kClamp_TileMode:
+ case kClampToBlack_TileMode:
+ filterPixels<UncheckedPixelFetcher>(src, result, offset, rect, bounds);
+ break;
+ }
+}
+
+void SkMatrixConvolutionImageFilter::filterBorderPixels(const SkBitmap& src,
+ SkBitmap* result,
+ SkIVector& offset,
+ const SkIRect& rect,
+ const SkIRect& srcBounds) const {
+ switch (fTileMode) {
+ case kClamp_TileMode:
+ filterPixels<ClampPixelFetcher>(src, result, offset, rect, srcBounds);
+ break;
+ case kRepeat_TileMode:
+ filterPixels<RepeatPixelFetcher>(src, result, offset, rect, srcBounds);
+ break;
+ case kClampToBlack_TileMode:
+ filterPixels<ClampToBlackPixelFetcher>(src, result, offset, rect, srcBounds);
+ break;
+ }
+}
+
+// FIXME: This should be refactored to SkImageFilterUtils for
+// use by other filters. For now, we assume the input is always
+// premultiplied and unpremultiply it
+static SkBitmap unpremultiply_bitmap(const SkBitmap& src) {
+ if (!src.getPixels()) {
+ return SkBitmap();
+ }
+
+ const SkImageInfo info = SkImageInfo::MakeN32(src.width(), src.height(), src.alphaType());
+ SkBitmap result;
+ if (!result.tryAllocPixels(info)) {
+ return SkBitmap();
+ }
+ for (int y = 0; y < src.height(); ++y) {
+ const uint32_t* srcRow = src.getAddr32(0, y);
+ uint32_t* dstRow = result.getAddr32(0, y);
+ for (int x = 0; x < src.width(); ++x) {
+ dstRow[x] = SkUnPreMultiply::PMColorToColor(srcRow[x]);
+ }
+ }
+ return result;
+}
+
+#if SK_SUPPORT_GPU
+
+static GrTextureDomain::Mode convert_tilemodes(SkMatrixConvolutionImageFilter::TileMode tileMode) {
+ switch (tileMode) {
+ case SkMatrixConvolutionImageFilter::kClamp_TileMode:
+ return GrTextureDomain::kClamp_Mode;
+ case SkMatrixConvolutionImageFilter::kRepeat_TileMode:
+ return GrTextureDomain::kRepeat_Mode;
+ case SkMatrixConvolutionImageFilter::kClampToBlack_TileMode:
+ return GrTextureDomain::kDecal_Mode;
+ default:
+ SkASSERT(false);
+ }
+ return GrTextureDomain::kIgnore_Mode;
+}
+#endif
+
+sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialImage* source,
+ const Context& ctx,
+ SkIPoint* offset) const {
+ SkIPoint inputOffset = SkIPoint::Make(0, 0);
+ sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
+ if (!input) {
+ return nullptr;
+ }
+
+ SkIRect dstBounds;
+ input = this->applyCropRectAndPad(this->mapContext(ctx), input.get(), &inputOffset, &dstBounds);
+ if (!input) {
+ return nullptr;
+ }
+
+ const SkIRect originalSrcBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY,
+ input->width(), input->height());
+
+ SkIRect srcBounds = this->onFilterNodeBounds(dstBounds, ctx.ctm(), kReverse_MapDirection,
+ &originalSrcBounds);
+
+ if (kRepeat_TileMode == fTileMode) {
+ srcBounds = DetermineRepeatedSrcBound(srcBounds, fKernelOffset,
+ fKernelSize, originalSrcBounds);
+ } else {
+ if (!srcBounds.intersect(dstBounds)) {
+ return nullptr;
+ }
+ }
+
+#if SK_SUPPORT_GPU
+ // Note: if the kernel is too big, the GPU path falls back to SW
+ if (source->isTextureBacked() &&
+ fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE) {
+ GrContext* context = source->getContext();
+
+ // Ensure the input is in the destination color space. Typically applyCropRect will have
+ // called pad_image to account for our dilation of bounds, so the result will already be
+ // moved to the destination color space. If a filter DAG avoids that, then we use this
+ // fall-back, which saves us from having to do the xform during the filter itself.
+ input = ImageToColorSpace(input.get(), ctx.outputProperties());
+
+ sk_sp<GrTextureProxy> inputProxy(input->asTextureProxyRef(context));
+ SkASSERT(inputProxy);
+
+ offset->fX = dstBounds.left();
+ offset->fY = dstBounds.top();
+ dstBounds.offset(-inputOffset);
+ srcBounds.offset(-inputOffset);
+
+ auto fp = GrMatrixConvolutionEffect::Make(std::move(inputProxy),
+ srcBounds,
+ fKernelSize,
+ fKernel,
+ fGain,
+ fBias,
+ fKernelOffset,
+ convert_tilemodes(fTileMode),
+ fConvolveAlpha);
+ if (!fp) {
+ return nullptr;
+ }
+
+ return DrawWithFP(context, std::move(fp), dstBounds, ctx.outputProperties());
+ }
+#endif
+
+ SkBitmap inputBM;
+
+ if (!input->getROPixels(&inputBM)) {
+ return nullptr;
+ }
+
+ if (inputBM.colorType() != kN32_SkColorType) {
+ return nullptr;
+ }
+
+ if (!fConvolveAlpha && !inputBM.isOpaque()) {
+ inputBM = unpremultiply_bitmap(inputBM);
+ }
+
+ if (!inputBM.getPixels()) {
+ return nullptr;
+ }
+
+ const SkImageInfo info = SkImageInfo::MakeN32(dstBounds.width(), dstBounds.height(),
+ inputBM.alphaType());
+
+ SkBitmap dst;
+ if (!dst.tryAllocPixels(info)) {
+ return nullptr;
+ }
+
+ offset->fX = dstBounds.fLeft;
+ offset->fY = dstBounds.fTop;
+ dstBounds.offset(-inputOffset);
+ srcBounds.offset(-inputOffset);
+
+ SkIRect interior;
+ if (kRepeat_TileMode == fTileMode) {
+ // In repeat mode, the filterPixels calls will wrap around
+ // so we just need to render 'dstBounds'
+ interior = dstBounds;
+ } else {
+ interior = SkIRect::MakeXYWH(dstBounds.left() + fKernelOffset.fX,
+ dstBounds.top() + fKernelOffset.fY,
+ dstBounds.width() - fKernelSize.fWidth + 1,
+ dstBounds.height() - fKernelSize.fHeight + 1);
+ }
+
+ SkIRect top = SkIRect::MakeLTRB(dstBounds.left(), dstBounds.top(),
+ dstBounds.right(), interior.top());
+ SkIRect bottom = SkIRect::MakeLTRB(dstBounds.left(), interior.bottom(),
+ dstBounds.right(), dstBounds.bottom());
+ SkIRect left = SkIRect::MakeLTRB(dstBounds.left(), interior.top(),
+ interior.left(), interior.bottom());
+ SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(),
+ dstBounds.right(), interior.bottom());
+
+ SkIVector dstContentOffset = { offset->fX - inputOffset.fX, offset->fY - inputOffset.fY };
+
+ this->filterBorderPixels(inputBM, &dst, dstContentOffset, top, srcBounds);
+ this->filterBorderPixels(inputBM, &dst, dstContentOffset, left, srcBounds);
+ this->filterInteriorPixels(inputBM, &dst, dstContentOffset, interior, srcBounds);
+ this->filterBorderPixels(inputBM, &dst, dstContentOffset, right, srcBounds);
+ this->filterBorderPixels(inputBM, &dst, dstContentOffset, bottom, srcBounds);
+
+ return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(), dstBounds.height()),
+ dst);
+}
+
+sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer)
+const {
+ SkASSERT(1 == this->countInputs());
+
+ sk_sp<SkImageFilter> input = xformer->apply(this->getInput(0));
+ if (input.get() != this->getInput(0)) {
+ return SkMatrixConvolutionImageFilter::Make(fKernelSize, fKernel, fGain, fBias,
+ fKernelOffset, fTileMode, fConvolveAlpha,
+ std::move(input), this->getCropRectIfSet());
+ }
+ return this->refMe();
+}
+
+SkIRect SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+ MapDirection dir,
+ const SkIRect* inputRect) const {
+ if (kReverse_MapDirection == dir && kRepeat_TileMode == fTileMode && inputRect) {
+ SkASSERT(inputRect);
+ return DetermineRepeatedSrcBound(src, fKernelOffset, fKernelSize, *inputRect);
+ }
+
+ SkIRect dst = src;
+ int w = fKernelSize.width() - 1, h = fKernelSize.height() - 1;
+
+ if (kReverse_MapDirection == dir) {
+ dst.adjust(-fKernelOffset.fX, -fKernelOffset.fY,
+ w - fKernelOffset.fX, h - fKernelOffset.fY);
+ } else {
+ dst.adjust(fKernelOffset.fX - w, fKernelOffset.fY - h, fKernelOffset.fX, fKernelOffset.fY);
+ }
+ return dst;
+}
+
+bool SkMatrixConvolutionImageFilter::affectsTransparentBlack() const {
+ // It seems that the only rational way for repeat sample mode to work is if the caller
+ // explicitly restricts the input in which case the input range is explicitly known and
+ // specified.
+ // TODO: is seems that this should be true for clamp mode too.
+
+ // For the other modes, because the kernel is applied in device-space, we have no idea what
+ // pixels it will affect in object-space.
+ return kRepeat_TileMode != fTileMode;
+}