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
This commit is contained in:
senorblanco@chromium.org 2012-09-18 20:32:34 +00:00
parent d1688744d5
commit 5faa2dc266
7 changed files with 409 additions and 0 deletions

View File

@ -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);

82
gm/matrixconvolution.cpp Normal file
View File

@ -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);
}

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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

View File

@ -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)