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:
parent
d1688744d5
commit
5faa2dc266
65
bench/MatrixConvolutionBench.cpp
Normal file
65
bench/MatrixConvolutionBench.cpp
Normal 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
82
gm/matrixconvolution.cpp
Normal 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);
|
||||
|
||||
}
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
76
include/effects/SkMatrixConvolutionImageFilter.h
Normal file
76
include/effects/SkMatrixConvolutionImageFilter.h
Normal 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
|
183
src/effects/SkMatrixConvolutionImageFilter.cpp
Normal file
183
src/effects/SkMatrixConvolutionImageFilter.cpp
Normal 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)
|
||||
|
Loading…
Reference in New Issue
Block a user