diff options
author | senorblanco@chromium.org <senorblanco@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2012-09-18 20:32:34 +0000 |
---|---|---|
committer | senorblanco@chromium.org <senorblanco@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2012-09-18 20:32:34 +0000 |
commit | 5faa2dc266ec933b3961f985e5718236f1ecbe47 (patch) | |
tree | c8c50ad2e8667e89aacadb5f8d0f242ba888097b | |
parent | d1688744d537d928699b6069f99c4470a0f6e772 (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.cpp | 65 | ||||
-rw-r--r-- | gm/matrixconvolution.cpp | 82 | ||||
-rw-r--r-- | gyp/bench.gypi | 1 | ||||
-rw-r--r-- | gyp/effects.gypi | 1 | ||||
-rw-r--r-- | gyp/gmslides.gypi | 1 | ||||
-rw-r--r-- | include/effects/SkMatrixConvolutionImageFilter.h | 76 | ||||
-rw-r--r-- | src/effects/SkMatrixConvolutionImageFilter.cpp | 183 |
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) + |