aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar senorblanco@chromium.org <senorblanco@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2012-09-18 20:32:34 +0000
committerGravatar senorblanco@chromium.org <senorblanco@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2012-09-18 20:32:34 +0000
commit5faa2dc266ec933b3961f985e5718236f1ecbe47 (patch)
treec8c50ad2e8667e89aacadb5f8d0f242ba888097b
parentd1688744d537d928699b6069f99c4470a0f6e772 (diff)
Implements a matrix convolution filter (raster path only). The filtering loop
is templated on the tiling mode for speed: interior pixels are unconditionally fetched; border pixels apply the appropriate tiling mode before fetching. It handles target, bias, divisor (as gain), and edge modes (named to be more skia-like). It does not handle the "preserveAlpha" semantics of feConvolveMatrix, nor "kernelUnitLength". git-svn-id: http://skia.googlecode.com/svn/trunk@5592 2bbb7eff-a529-9590-31e7-b0007b416f81
-rw-r--r--bench/MatrixConvolutionBench.cpp65
-rw-r--r--gm/matrixconvolution.cpp82
-rw-r--r--gyp/bench.gypi1
-rw-r--r--gyp/effects.gypi1
-rw-r--r--gyp/gmslides.gypi1
-rw-r--r--include/effects/SkMatrixConvolutionImageFilter.h76
-rw-r--r--src/effects/SkMatrixConvolutionImageFilter.cpp183
7 files changed, 409 insertions, 0 deletions
diff --git a/bench/MatrixConvolutionBench.cpp b/bench/MatrixConvolutionBench.cpp
new file mode 100644
index 0000000000..1f6a004270
--- /dev/null
+++ b/bench/MatrixConvolutionBench.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkBenchmark.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+#include "SkString.h"
+#include "SkMatrixConvolutionImageFilter.h"
+
+class MatrixConvolutionBench : public SkBenchmark {
+ SkMatrixConvolutionImageFilter::TileMode fTileMode;
+
+public:
+ MatrixConvolutionBench(void* param, SkMatrixConvolutionImageFilter::TileMode tileMode)
+ : INHERITED(param), fName("matrixconvolution") {
+ SkISize kernelSize = SkISize::Make(3, 3);
+ SkScalar kernel[9] = {
+ SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
+ SkIntToScalar( 1), SkIntToScalar(-7), SkIntToScalar( 1),
+ SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
+ };
+ SkScalar gain = SkFloatToScalar(0.3f), bias = SkIntToScalar(100);
+ SkIPoint target = SkIPoint::Make(1, 1);
+ fFilter = new SkMatrixConvolutionImageFilter(kernelSize, kernel, gain, bias, target, tileMode);
+ }
+
+ ~MatrixConvolutionBench() {
+ fFilter->unref();
+ }
+
+protected:
+ virtual const char* onGetName() {
+ return fName.c_str();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ SkPaint paint;
+ this->setupPaint(&paint);
+ paint.setAntiAlias(true);
+ SkRandom rand;
+ for (int i = 0; i < SkBENCHLOOP(3); i++) {
+ SkRect r = SkRect::MakeWH(rand.nextUScalar1() * 400,
+ rand.nextUScalar1() * 400);
+ paint.setImageFilter(fFilter);
+ canvas->drawOval(r, paint);
+ }
+ }
+
+private:
+ typedef SkBenchmark INHERITED;
+ SkMatrixConvolutionImageFilter* fFilter;
+ SkString fName;
+};
+
+static SkBenchmark* Fact00(void* p) { return new MatrixConvolutionBench(p, SkMatrixConvolutionImageFilter::kClamp_TileMode); }
+static SkBenchmark* Fact01(void* p) { return new MatrixConvolutionBench(p, SkMatrixConvolutionImageFilter::kRepeat_TileMode); }
+static SkBenchmark* Fact02(void* p) { return new MatrixConvolutionBench(p, SkMatrixConvolutionImageFilter::kClampToBlack_TileMode); }
+
+static BenchRegistry gReg00(Fact00);
+static BenchRegistry gReg01(Fact01);
+static BenchRegistry gReg02(Fact02);
diff --git a/gm/matrixconvolution.cpp b/gm/matrixconvolution.cpp
new file mode 100644
index 0000000000..6c69fac485
--- /dev/null
+++ b/gm/matrixconvolution.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkMatrixConvolutionImageFilter.h"
+
+namespace skiagm {
+
+class MatrixConvolutionGM : public GM {
+public:
+ MatrixConvolutionGM() : fInitialized(false) {
+ this->setBGColor(0x00000000);
+ }
+
+protected:
+ virtual SkString onShortName() {
+ return SkString("matrixconvolution");
+ }
+
+ void make_bitmap() {
+ fBitmap.setConfig(SkBitmap::kARGB_8888_Config, 80, 80);
+ fBitmap.allocPixels();
+ SkDevice device(fBitmap);
+ SkCanvas canvas(&device);
+ canvas.clear(0x00000000);
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(0xFFFFFFFF);
+ paint.setTextSize(SkIntToScalar(180));
+ const char* str = "e";
+ canvas.drawText(str, strlen(str), SkIntToScalar(-10), SkIntToScalar(80), paint);
+ }
+
+ virtual SkISize onISize() {
+ return make_isize(300, 300);
+ }
+
+ void draw(SkCanvas* canvas, int x, int y, const SkIPoint& target, SkMatrixConvolutionImageFilter::TileMode tileMode) {
+ SkScalar kernel[9] = {
+ SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
+ SkIntToScalar( 1), SkIntToScalar(-7), SkIntToScalar( 1),
+ SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
+ };
+ SkISize kernelSize = SkISize::Make(3, 3);
+ SkScalar gain = SkFloatToScalar(0.3f), bias = SkIntToScalar(100);
+ SkPaint paint;
+ SkAutoTUnref<SkImageFilter> filter(SkNEW_ARGS(SkMatrixConvolutionImageFilter, (kernelSize, kernel, gain, bias, target, tileMode)));
+ paint.setImageFilter(filter);
+ canvas->drawSprite(fBitmap, x, y, &paint);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ if (!fInitialized) {
+ make_bitmap();
+ fInitialized = true;
+ }
+ canvas->clear(0x00000000);
+ SkIPoint target = SkIPoint::Make(1, 1);
+ for (target.fY = 0; target.fY < 3; ++target.fY) {
+ int y = target.fY * 100 + 10;
+ draw(canvas, 10, y, target, SkMatrixConvolutionImageFilter::kClamp_TileMode);
+ draw(canvas, 110, y, target, SkMatrixConvolutionImageFilter::kClampToBlack_TileMode);
+ draw(canvas, 210, y, target, SkMatrixConvolutionImageFilter::kRepeat_TileMode);
+ }
+ }
+
+private:
+ typedef GM INHERITED;
+ SkBitmap fBitmap;
+ bool fInitialized;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new MatrixConvolutionGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gyp/bench.gypi b/gyp/bench.gypi
index 81a00edcb1..ecb533a578 100644
--- a/gyp/bench.gypi
+++ b/gyp/bench.gypi
@@ -20,6 +20,7 @@
'../bench/InterpBench.cpp',
'../bench/MathBench.cpp',
'../bench/MatrixBench.cpp',
+ '../bench/MatrixConvolutionBench.cpp',
'../bench/MemoryBench.cpp',
'../bench/MorphologyBench.cpp',
'../bench/MutexBench.cpp',
diff --git a/gyp/effects.gypi b/gyp/effects.gypi
index 164fc82ed0..aef7f2c126 100644
--- a/gyp/effects.gypi
+++ b/gyp/effects.gypi
@@ -33,6 +33,7 @@
'<(skia_src_path)/effects/SkLayerDrawLooper.cpp',
'<(skia_src_path)/effects/SkLayerRasterizer.cpp',
'<(skia_src_path)/effects/SkLightingImageFilter.cpp',
+ '<(skia_src_path)/effects/SkMatrixConvolutionImageFilter.cpp',
'<(skia_src_path)/effects/SkMorphologyImageFilter.cpp',
'<(skia_src_path)/effects/SkPaintFlagsDrawFilter.cpp',
'<(skia_src_path)/effects/SkPixelXorXfermode.cpp',
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index a0d418ba83..003cb05464 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -46,6 +46,7 @@
'../gm/imagefiltersgraph.cpp',
'../gm/lcdtext.cpp',
'../gm/linepaths.cpp',
+ '../gm/matrixconvolution.cpp',
'../gm/morphology.cpp',
'../gm/ninepatchstretch.cpp',
'../gm/nocolorbleed.cpp',
diff --git a/include/effects/SkMatrixConvolutionImageFilter.h b/include/effects/SkMatrixConvolutionImageFilter.h
new file mode 100644
index 0000000000..4409ebd40e
--- /dev/null
+++ b/include/effects/SkMatrixConvolutionImageFilter.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#ifndef SkMatrixConvolutionImageFilter_DEFINED
+#define SkMatrixConvolutionImageFilter_DEFINED
+
+#include "SkSingleInputImageFilter.h"
+#include "SkScalar.h"
+#include "SkSize.h"
+#include "SkPoint.h"
+
+/*! \class SkMatrixConvolutionImageFilter
+ Matrix convolution image filter. This filter applies an NxM image
+ processing kernel to a given input image. This can be used to produce
+ effects such as sharpening, blurring, edge detection, etc.
+ */
+
+class SkMatrixConvolutionImageFilter : public SkSingleInputImageFilter {
+public:
+ /*! \enum TileMode */
+ enum TileMode {
+ kClamp_TileMode, /*!< Clamp to the image's edge pixels. */
+ kRepeat_TileMode, /*!< Wrap around to the image's opposite edge. */
+ kClampToBlack_TileMode, /*!< Fill with transparent black. */
+ };
+
+ /** Construct a matrix convolution image filter.
+ @param kernelSize The kernel size in pixels, in each dimension (N by M).
+ @param kernel The image processing kernel. Must contain N * M
+ elements, in row order.
+ @param gain A scale factor applied to each pixel after
+ convolution. This can be used to normalize the
+ kernel, if it does not sum to 1.
+ @param bias A bias factor added to each pixel after convolution.
+ @param target An offset applied to each pixel coordinate before
+ convolution. This can be used to center the kernel
+ over the image (e.g., a 3x3 kernel should have a
+ target of {1, 1}).
+ @param tileMode How accesses outside the image are treated. (@see
+ TileMode).
+ @param input The input image filter. If NULL, the src bitmap
+ passed to filterImage() is used instead.
+ */
+
+ SkMatrixConvolutionImageFilter(const SkISize& kernelSize, const SkScalar* kernel, SkScalar gain, SkScalar bias, const SkIPoint& target, TileMode tileMode, SkImageFilter* input = NULL);
+ virtual ~SkMatrixConvolutionImageFilter();
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkMatrixConvolutionImageFilter)
+
+protected:
+ SkMatrixConvolutionImageFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+ virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+ SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+
+private:
+ SkISize fKernelSize;
+ SkScalar* fKernel;
+ SkScalar fGain;
+ SkScalar fBias;
+ SkIPoint fTarget;
+ TileMode fTileMode;
+ typedef SkSingleInputImageFilter INHERITED;
+
+ template <class PixelFetcher>
+ void filterPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect);
+ void filterInteriorPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect);
+ void filterBorderPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect);
+};
+
+#endif
diff --git a/src/effects/SkMatrixConvolutionImageFilter.cpp b/src/effects/SkMatrixConvolutionImageFilter.cpp
new file mode 100644
index 0000000000..3ff13d2236
--- /dev/null
+++ b/src/effects/SkMatrixConvolutionImageFilter.cpp
@@ -0,0 +1,183 @@
+/*
+ * 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 "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkRect.h"
+
+SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(const SkISize& kernelSize, const SkScalar* kernel, SkScalar gain, SkScalar bias, const SkIPoint& target, TileMode tileMode, SkImageFilter* input)
+ : INHERITED(input),
+ fKernelSize(kernelSize),
+ fGain(gain),
+ fBias(bias),
+ fTarget(target),
+ fTileMode(tileMode) {
+ uint32_t size = fKernelSize.fWidth * fKernelSize.fHeight;
+ fKernel = SkNEW_ARRAY(SkScalar, size);
+ memcpy(fKernel, kernel, size * sizeof(SkScalar));
+ SkASSERT(target.fX >= 0 && target.fX < kernelSize.fWidth);
+ SkASSERT(target.fY >= 0 && target.fY < kernelSize.fHeight);
+}
+
+SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fKernelSize.fWidth = buffer.readInt();
+ fKernelSize.fHeight = buffer.readInt();
+ uint32_t size = fKernelSize.fWidth * fKernelSize.fHeight;
+ fKernel = SkNEW_ARRAY(SkScalar, size);
+ uint32_t readSize = buffer.readScalarArray(fKernel);
+ SkASSERT(readSize == size);
+ fGain = buffer.readScalar();
+ fBias = buffer.readScalar();
+ fTarget.fX = buffer.readScalar();
+ fTarget.fY = buffer.readScalar();
+ fTileMode = (TileMode) buffer.readInt();
+}
+
+void SkMatrixConvolutionImageFilter::flatten(SkFlattenableWriteBuffer& 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.writeScalar(fTarget.fX);
+ buffer.writeScalar(fTarget.fY);
+ buffer.writeInt((int) fTileMode);
+}
+
+SkMatrixConvolutionImageFilter::~SkMatrixConvolutionImageFilter() {
+ delete[] fKernel;
+}
+
+class UncheckedPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ return *src.getAddr32(x, y);
+ }
+};
+
+class ClampPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ x = SkClampMax(x, src.width() - 1);
+ y = SkClampMax(y, src.height() - 1);
+ return *src.getAddr32(x, y);
+ }
+};
+
+class RepeatPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ x %= src.width();
+ y %= src.height();
+ if (x < 0) {
+ x += src.width();
+ }
+ if (y < 0) {
+ y += src.height();
+ }
+ return *src.getAddr32(x, y);
+ }
+};
+
+class ClampToBlackPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ if (x < 0 || x >= src.width() || y < 0 || y >= src.height()) {
+ return 0;
+ } else {
+ return *src.getAddr32(x, y);
+ }
+ }
+};
+
+template<class PixelFetcher>
+void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ for (int y = rect.fTop; y < rect.fBottom; ++y) {
+ SkPMColor* dptr = result->getAddr32(rect.fLeft, y);
+ 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 - fTarget.fX, y + cy - fTarget.fY);
+ SkScalar k = fKernel[cy * fKernelSize.fWidth + cx];
+ sumA += SkScalarMul(SkIntToScalar(SkGetPackedA32(s)), k);
+ sumR += SkScalarMul(SkIntToScalar(SkGetPackedR32(s)), k);
+ sumG += SkScalarMul(SkIntToScalar(SkGetPackedG32(s)), k);
+ sumB += SkScalarMul(SkIntToScalar(SkGetPackedB32(s)), k);
+ }
+ }
+ int a = SkScalarFloorToInt(SkScalarMul(sumA, fGain) + fBias);
+ int r = SkScalarFloorToInt(SkScalarMul(sumR, fGain) + fBias);
+ int g = SkScalarFloorToInt(SkScalarMul(sumG, fGain) + fBias);
+ int b = SkScalarFloorToInt(SkScalarMul(sumB, fGain) + fBias);
+ *dptr++ = SkPackARGB32(SkClampMax(a, 255),
+ SkClampMax(r, 255),
+ SkClampMax(g, 255),
+ SkClampMax(b, 255));
+ }
+ }
+}
+
+void SkMatrixConvolutionImageFilter::filterInteriorPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ filterPixels<UncheckedPixelFetcher>(src, result, rect);
+}
+
+void SkMatrixConvolutionImageFilter::filterBorderPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ switch (fTileMode) {
+ case kClamp_TileMode:
+ filterPixels<ClampPixelFetcher>(src, result, rect);
+ break;
+ case kRepeat_TileMode:
+ filterPixels<RepeatPixelFetcher>(src, result, rect);
+ break;
+ case kClampToBlack_TileMode:
+ filterPixels<ClampToBlackPixelFetcher>(src, result, rect);
+ break;
+ }
+}
+
+bool SkMatrixConvolutionImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = this->getInputResult(proxy, source, matrix, loc);
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ result->setConfig(src.config(), src.width(), src.height());
+ result->allocPixels();
+
+ SkIRect interior = SkIRect::MakeXYWH(fTarget.fX, fTarget.fY,
+ src.width() - fKernelSize.fWidth + 1,
+ src.height() - fKernelSize.fHeight + 1);
+ SkIRect top = SkIRect::MakeWH(src.width(), fTarget.fY);
+ SkIRect bottom = SkIRect::MakeLTRB(0, interior.bottom(),
+ src.width(), src.height());
+ SkIRect left = SkIRect::MakeXYWH(0, interior.top(),
+ fTarget.fX, interior.height());
+ SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(),
+ src.width(), interior.bottom());
+ filterBorderPixels(src, result, top);
+ filterBorderPixels(src, result, left);
+ filterInteriorPixels(src, result, interior);
+ filterBorderPixels(src, result, right);
+ filterBorderPixels(src, result, bottom);
+ return true;
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR(SkMatrixConvolutionImageFilter)
+