Complete the implementation of the faster blur; now supports all blur styles and matches the boxfilter approximation visually. Also change the interpretation of the blur radius to be sigma/2; need to add SK_IGNORE_BLUR_RADIUS_CORRECTNESS to chromium GYP to avoid immediate layout test failures over there.

Review URL: https://codereview.appspot.com/7307076

git-svn-id: http://skia.googlecode.com/svn/trunk@7793 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
humper@google.com 2013-02-20 16:42:06 +00:00
parent f33612b923
commit a99a92ceba
4 changed files with 662 additions and 197 deletions

View File

@ -74,13 +74,13 @@ private:
class BlurRectDirectBench: public BlurRectBench {
public:
BlurRectDirectBench(void *param, SkScalar rad) : BlurRectBench(param, rad) {
BlurRectDirectBench(void *param, SkScalar rad) : INHERITED(param, rad) {
SkString name;
if (SkScalarFraction(rad) != 0) {
name.printf("blurrect_direct_%.2f", SkScalarToFloat(rad));
} else {
name.printf("blurrect_direct_%d", SkScalarRound(rad));
name.printf("blurrect_direct_%d", SkScalarRoundToInt(rad));
}
setName(name);
@ -88,23 +88,17 @@ class BlurRectDirectBench: public BlurRectBench {
protected:
virtual void makeBlurryRect(const SkRect& r) SK_OVERRIDE {
SkMask mask;
SkBlurMask::BlurRect(&mask, r, radius(), SkBlurMask::kNormal_Style,
SkBlurMask::kHigh_Quality);
SkBlurMask::BlurRect(&mask, r, this->radius(), SkBlurMask::kNormal_Style);
SkMask::FreeImage(mask.fImage);
}
private:
typedef BlurRectBench INHERITED;
};
class BlurRectSeparableBench: public BlurRectBench {
SkMask fSrcMask;
public:
BlurRectSeparableBench(void *param, SkScalar rad) : BlurRectBench(param, rad) {
SkString name;
if (SkScalarFraction(rad) != 0) {
name.printf("blurrect_separable_%.2f", SkScalarToFloat(rad));
} else {
name.printf("blurrect_separable_%d", SkScalarRound(rad));
}
setName(name);
BlurRectSeparableBench(void *param, SkScalar rad) : INHERITED(param, rad) {
fSrcMask.fImage = NULL;
}
@ -123,21 +117,106 @@ protected:
memset(fSrcMask.fImage, 0xff, fSrcMask.computeTotalImageSize());
}
SkMask fSrcMask;
private:
typedef BlurRectBench INHERITED;
};
class BlurRectBoxFilterBench: public BlurRectSeparableBench {
public:
BlurRectBoxFilterBench(void *param, SkScalar rad) : INHERITED(param, rad) {
SkString name;
if (SkScalarFraction(rad) != 0) {
name.printf("blurrect_boxfilter_%.2f", SkScalarToFloat(rad));
} else {
name.printf("blurrect_boxfilter_%d", SkScalarRoundToInt(rad));
}
setName(name);
}
protected:
virtual void makeBlurryRect(const SkRect& r) SK_OVERRIDE {
SkMask mask;
SkBlurMask::BlurSeparable(&mask, fSrcMask, radius(),
mask.fImage = NULL;
SkBlurMask::BlurSeparable(&mask, fSrcMask, this->radius(),
SkBlurMask::kNormal_Style,
SkBlurMask::kHigh_Quality);
SkMask::FreeImage(mask.fImage);
}
private:
typedef BlurRectSeparableBench INHERITED;
};
DEF_BENCH(return new BlurRectSeparableBench(p, SMALL);)
DEF_BENCH(return new BlurRectSeparableBench(p, BIG);)
DEF_BENCH(return new BlurRectSeparableBench(p, REALBIG);)
DEF_BENCH(return new BlurRectSeparableBench(p, REAL);)
class BlurRectGaussianBench: public BlurRectSeparableBench {
public:
BlurRectGaussianBench(void *param, SkScalar rad) : INHERITED(param, rad) {
SkString name;
if (SkScalarFraction(rad) != 0) {
name.printf("blurrect_gaussian_%.2f", SkScalarToFloat(rad));
} else {
name.printf("blurrect_gaussian_%d", SkScalarRoundToInt(rad));
}
setName(name);
}
protected:
virtual void makeBlurryRect(const SkRect& r) SK_OVERRIDE {
SkMask mask;
mask.fImage = NULL;
SkBlurMask::BlurGroundTruth(&mask, fSrcMask, this->radius(),
SkBlurMask::kNormal_Style);
SkMask::FreeImage(mask.fImage);
}
private:
typedef BlurRectSeparableBench INHERITED;
};
DEF_BENCH(return new BlurRectBoxFilterBench(p, SMALL);)
DEF_BENCH(return new BlurRectBoxFilterBench(p, BIG);)
DEF_BENCH(return new BlurRectBoxFilterBench(p, REALBIG);)
DEF_BENCH(return new BlurRectBoxFilterBench(p, REAL);)
DEF_BENCH(return new BlurRectGaussianBench(p, SMALL);)
DEF_BENCH(return new BlurRectGaussianBench(p, BIG);)
DEF_BENCH(return new BlurRectGaussianBench(p, REALBIG);)
DEF_BENCH(return new BlurRectGaussianBench(p, REAL);)
DEF_BENCH(return new BlurRectDirectBench(p, SMALL);)
DEF_BENCH(return new BlurRectDirectBench(p, BIG);)
DEF_BENCH(return new BlurRectDirectBench(p, REALBIG);)
DEF_BENCH(return new BlurRectDirectBench(p, REAL);)
DEF_BENCH(return new BlurRectDirectBench(p, SkIntToScalar(5));)
DEF_BENCH(return new BlurRectDirectBench(p, SkIntToScalar(20));)
DEF_BENCH(return new BlurRectBoxFilterBench(p, SkIntToScalar(5));)
DEF_BENCH(return new BlurRectBoxFilterBench(p, SkIntToScalar(20));)
#if 0
// disable Gaussian benchmarks; the algorithm works well enough
// and serves as a baseline for ground truth, but it's too slow
// to use in production for non-trivial radii, so no real point
// in having the bots benchmark it all the time.
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(1));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(2));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(3));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(4));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(5));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(6));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(7));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(8));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(9));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(10));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(11));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(12));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(13));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(14));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(15));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(16));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(17));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(18));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(19));)
DEF_BENCH(return new BlurRectGaussianBench(p, SkIntToScalar(20));)
#endif

View File

@ -1,9 +1,9 @@
/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
* 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 "SkBlurMaskFilter.h"
@ -65,19 +65,18 @@ static const char* gBlurStyle2Name[] = {
};
class BlurRectGM : public skiagm::GM {
SkAutoTUnref<SkMaskFilter> fMaskFilter;
SkString fName;
PaintProc fPProc;
SkAlpha fAlpha;
SkAutoTUnref<SkMaskFilter> fMaskFilter;
SkString fName;
PaintProc fPProc;
SkAlpha fAlpha;
public:
BlurRectGM(const char name[], PaintProc pproc, U8CPU alpha,
SkBlurMaskFilter::BlurStyle bs) :
fMaskFilter(SkBlurMaskFilter::Create(STROKE_WIDTH/2, bs,
SkBlurMaskFilter::kHighQuality_BlurFlag))
, fName(name)
, fPProc(pproc)
, fAlpha(SkToU8(alpha))
{
SkBlurMaskFilter::kHighQuality_BlurFlag))
, fName(name)
, fPProc(pproc)
, fAlpha(SkToU8(alpha)) {
fName.appendf("_%s", gBlurStyle2Name[bs]);
}
@ -131,7 +130,7 @@ private:
canvas->translate(0, r.height() * 4/3);
}
}
private:
typedef GM INHERITED;
};
@ -139,17 +138,28 @@ class BlurRectCompareGM : public skiagm::GM {
SkString fName;
unsigned int fRectWidth, fRectHeight;
SkScalar fRadius;
SkBlurMask::Style fStyle;
public:
BlurRectCompareGM(const char name[], unsigned int rectWidth, unsigned int rectHeight, float radius)
BlurRectCompareGM(const char name[], unsigned int rectWidth, unsigned int rectHeight, float radius, SkBlurMask::Style style)
: fName(name)
, fRectWidth(rectWidth)
, fRectHeight(rectHeight)
, fRadius(radius)
{}
, fStyle(style)
{}
int width() const { return fRectWidth; }
int height() const { return fRectHeight; }
SkScalar radius() const { return fRadius; }
int width() const {
return fRectWidth;
}
int height() const {
return fRectHeight;
}
SkScalar radius() const {
return fRadius;
}
SkBlurMask::Style style() const {
return fStyle;
}
protected:
virtual SkString onShortName() {
@ -160,21 +170,34 @@ protected:
return SkISize::Make(640, 480);
}
virtual void makeMask(SkMask *m, const SkRect&) = 0;
virtual bool makeMask(SkMask *m, const SkRect&) = 0;
virtual void onDraw(SkCanvas* canvas) {
SkRect r;
r.setWH(SkIntToScalar(fRectWidth), SkIntToScalar(fRectHeight));
SkRect r;
r.setWH(SkIntToScalar(fRectWidth), SkIntToScalar(fRectHeight));
SkMask mask;
SkISize canvas_size = canvas->getDeviceSize();
int center_x = (canvas_size.fWidth - r.width())/2;
int center_y = (canvas_size.fHeight - r.height())/2;
this->makeMask(&mask, r);
SkAutoMaskFreeImage amfi(mask.fImage);
SkMask mask;
SkBitmap bm;
bm.setConfig(SkBitmap::kA8_Config, mask.fBounds.width(), mask.fBounds.height());
bm.setPixels(mask.fImage);
canvas->drawBitmap(bm, 50, 50, NULL);
if (!this->makeMask(&mask, r)) {
SkPaint paint;
r.offset( center_x, center_y );
canvas->drawRect(r,paint);
return;
}
SkAutoMaskFreeImage amfi(mask.fImage);
SkBitmap bm;
bm.setConfig(SkBitmap::kA8_Config, mask.fBounds.width(), mask.fBounds.height());
bm.setPixels(mask.fImage);
center_x = (canvas_size.fWidth - mask.fBounds.width())/2;
center_y = (canvas_size.fHeight - mask.fBounds.height())/2;
canvas->drawBitmap(bm, center_x, center_y, NULL);
}
virtual uint32_t onGetFlags() const { return kSkipPipe_Flag; }
@ -186,33 +209,94 @@ private:
class BlurRectFastGM: public BlurRectCompareGM {
public:
BlurRectFastGM(const char name[], unsigned int rect_width,
unsigned int rect_height, float blur_radius) :
BlurRectCompareGM(name, rect_width, rect_height, blur_radius) {}
unsigned int rect_height, float blur_radius,
SkBlurMask::Style style) :
INHERITED(name, rect_width, rect_height, blur_radius, style)
{
}
protected:
virtual void makeMask(SkMask *m, const SkRect& r) SK_OVERRIDE {
SkBlurMask::BlurRect(m, r, radius(), SkBlurMask::kNormal_Style,
SkBlurMask::kHigh_Quality );
virtual bool makeMask(SkMask *m, const SkRect& r) SK_OVERRIDE {
return SkBlurMask::BlurRect(m, r, this->radius(), this->style());
}
private:
typedef BlurRectCompareGM INHERITED;
};
class BlurRectSlowGM: public BlurRectCompareGM {
public:
BlurRectSlowGM(const char name[], unsigned int rect_width, unsigned int rect_height, float blur_radius) :
BlurRectCompareGM( name, rect_width, rect_height, blur_radius ) {}
BlurRectSlowGM(const char name[], unsigned int rect_width, unsigned int rect_height,
float blur_radius, SkBlurMask::Style style) :
INHERITED(name, rect_width, rect_height, blur_radius, style)
{
}
protected:
virtual void makeMask(SkMask *m, const SkRect& r) SK_OVERRIDE {
virtual bool makeMask(SkMask *m, const SkRect& r) SK_OVERRIDE {
SkMask src;
r.roundOut(&src.fBounds);
src.fBounds.offset(-src.fBounds.fLeft, -src.fBounds.fTop); // move to origin
src.fFormat = SkMask::kA8_Format;
src.fRowBytes = src.fBounds.width();
src.fImage = SkMask::AllocImage( src.computeTotalImageSize() );
src.fImage = SkMask::AllocImage(src.computeTotalImageSize());
SkAutoMaskFreeImage amfi(src.fImage);
memset(src.fImage, 0xff, src.computeTotalImageSize());
SkBlurMask::BlurSeparable(m, src, radius()/2, SkBlurMask::kNormal_Style, SkBlurMask::kHigh_Quality);
return SkBlurMask::BlurSeparable(m, src, this->radius(), this->style(), this->getQuality());
}
virtual SkBlurMask::Quality getQuality() {
return SkBlurMask::kHigh_Quality;
}
private:
typedef BlurRectCompareGM INHERITED;
};
class BlurRectSlowLowGM: public BlurRectSlowGM {
public:
BlurRectSlowLowGM(const char name[], unsigned int rectWidth, unsigned int rectHeight,
float blurRadius, SkBlurMask::Style style) :
INHERITED(name, rectWidth, rectHeight, blurRadius, style)
{
}
protected:
virtual SkBlurMask::Quality getQuality() SK_OVERRIDE {
return SkBlurMask::kLow_Quality;
}
private:
typedef BlurRectSlowGM INHERITED;
};
class BlurRectGroundTruthGM: public BlurRectCompareGM {
public:
BlurRectGroundTruthGM(const char name[], unsigned int rectWidth, unsigned int rectHeight,
float blurRadius, SkBlurMask::Style style) :
INHERITED(name, rectWidth, rectHeight, blurRadius, style)
{
}
protected:
virtual bool makeMask(SkMask *m, const SkRect& r) SK_OVERRIDE {
SkMask src;
r.roundOut(&src.fBounds);
src.fBounds.offset(-src.fBounds.fLeft, -src.fBounds.fTop); // move to origin
src.fFormat = SkMask::kA8_Format;
src.fRowBytes = src.fBounds.width();
src.fImage = SkMask::AllocImage(src.computeTotalImageSize());
SkAutoMaskFreeImage amfi(src.fImage);
memset(src.fImage, 0xff, src.computeTotalImageSize());
return SkBlurMask::BlurGroundTruth(m, src, this->radius(), this->style());
}
virtual SkBlurMask::Quality getQuality() {
return SkBlurMask::kHigh_Quality;
}
private:
typedef BlurRectCompareGM INHERITED;
};
@ -223,12 +307,73 @@ DEF_GM(return new BlurRectGM("blurrect", NULL, 0xFF, SkBlurMaskFilter::kSolid_Bl
DEF_GM(return new BlurRectGM("blurrect", NULL, 0xFF, SkBlurMaskFilter::kOuter_BlurStyle);)
DEF_GM(return new BlurRectGM("blurrect", NULL, 0xFF, SkBlurMaskFilter::kInner_BlurStyle);)
DEF_GM(return new BlurRectFastGM("blurrect_fast_100_100_10", 100, 100, 10);)
DEF_GM(return new BlurRectFastGM("blurrect_fast_100_100_2", 100, 100, 2);)
DEF_GM(return new BlurRectFastGM("blurrect_fast_10_10_100", 10, 10, 100);)
DEF_GM(return new BlurRectFastGM("blurrect_fast_10_100_10", 10, 100, 10);)
// regular size rects, blurs should be small enough not to completely overlap.
DEF_GM(return new BlurRectSlowGM("blurrect_slow_100_100_10", 100, 100, 10);)
DEF_GM(return new BlurRectSlowGM("blurrect_slow_100_100_2", 100, 100, 2);)
DEF_GM(return new BlurRectSlowGM("blurrect_slow_10_10_100", 10, 10, 100);)
DEF_GM(return new BlurRectSlowGM("blurrect_slow_10_100_10", 10, 100, 10);)
DEF_GM(return new BlurRectFastGM( "blurrect_25_100_2_normal_fast", 25, 100, 2, SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_25_100_20_normal_fast", 25, 100, 20, SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_25_100_2_normal_slow", 25, 100, 2, SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_25_100_20_normal_slow", 25, 100, 20, SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectFastGM( "blurrect_25_100_2_inner_fast", 25, 100, 2, SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_25_100_20_inner_fast", 25, 100, 20, SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_25_100_2_inner_slow", 25, 100, 2, SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_25_100_20_inner_slow", 25, 100, 20, SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectFastGM( "blurrect_25_100_2_outer_fast", 25, 100, 2, SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_25_100_20_outer_fast", 25, 100, 20, SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_25_100_2_outer_slow", 25, 100, 2, SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_25_100_20_outer_slow", 25, 100, 20, SkBlurMask::kOuter_Style);)
// skinny tall rects, blurs overlap in X but not y
DEF_GM(return new BlurRectFastGM( "blurrect_5_100_2_normal_fast", 5, 100, 2 , SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_5_100_20_normal_fast", 5, 100, 20, SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_5_100_2_normal_slow", 5, 100, 2 , SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_5_100_20_normal_slow", 5, 100, 20, SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectFastGM( "blurrect_5_100_2_inner_fast", 5, 100, 2 , SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_5_100_20_inner_fast", 5, 100, 20, SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_5_100_2_inner_slow", 5, 100, 2 , SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_5_100_20_inner_slow", 5, 100, 20, SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectFastGM( "blurrect_5_100_2_outer_fast", 5, 100, 2 , SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_5_100_20_outer_fast", 5, 100, 20, SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_5_100_2_outer_slow", 5, 100, 2 , SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_5_100_20_outer_slow", 5, 100, 20, SkBlurMask::kOuter_Style);)
// tiny rects, blurs overlap in X and Y
DEF_GM(return new BlurRectFastGM( "blurrect_5_5_2_normal_fast", 5, 5, 2 , SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_5_5_20_normal_fast", 5, 5, 20, SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_5_5_2_normal_slow", 5, 5, 2 , SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_5_5_20_normal_slow", 5, 5, 20, SkBlurMask::kNormal_Style);)
DEF_GM(return new BlurRectFastGM( "blurrect_5_5_2_inner_fast", 5, 5, 2 , SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_5_5_20_inner_fast", 5, 5, 20, SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_5_5_2_inner_slow", 5, 5, 2 , SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_5_5_20_inner_slow", 5, 5, 20, SkBlurMask::kInner_Style);)
DEF_GM(return new BlurRectFastGM( "blurrect_5_5_2_outer_fast", 5, 5, 2 , SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectFastGM("blurrect_5_5_20_outer_fast", 5, 5, 20, SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectSlowGM( "blurrect_5_5_2_outer_slow", 5, 5, 2 , SkBlurMask::kOuter_Style);)
DEF_GM(return new BlurRectSlowGM("blurrect_5_5_20_outer_slow", 5, 5, 20, SkBlurMask::kOuter_Style);)
#if 0
// dont' need to GM the gaussian convolution; it's slow and intended
// as a ground truth comparison only. Leaving these here in case we
// ever want to turn these back on for debugging reasons.
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_1_simple", 25, 100, 1);)
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_2_simple", 25, 100, 2);)
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_3_simple", 25, 100, 3);)
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_4_simple", 25, 100, 4);)
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_5_simple", 25, 100, 5);)
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_6_simple", 25, 100, 6);)
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_7_simple", 25, 100, 7);)
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_8_simple", 25, 100, 8);)
DEF_GM(return new BlurRectGroundTruthGM( "blurrect_25_100_9_simple", 25, 100, 9);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_10_simple", 25, 100, 10);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_11_simple", 25, 100, 11);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_12_simple", 25, 100, 12);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_13_simple", 25, 100, 13);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_14_simple", 25, 100, 14);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_15_simple", 25, 100, 15);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_16_simple", 25, 100, 16);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_17_simple", 25, 100, 17);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_18_simple", 25, 100, 18);)
DEF_GM(return new BlurRectGroundTruthGM("blurrect_25_100_19_simple", 25, 100, 19);)
#endif

View File

@ -210,7 +210,7 @@ static int boxBlur(const uint8_t* src, int src_y_stride, uint8_t* dst,
RIGHT_BORDER_ITER
}
#undef RIGHT_BORDER_ITER
for (int x = 0; x < leftRadius - rightRadius; x++) {
for (int x = 0; x < leftRadius - rightRadius; ++x) {
*dptr = 0;
dptr += dst_x_stride;
}
@ -326,7 +326,7 @@ static int boxBlurInterp(const uint8_t* src, int src_y_stride, uint8_t* dst,
}
#endif
for (;x < border; x++) {
for (;x < border; ++x) {
LEFT_BORDER_ITER
}
#undef LEFT_BORDER_ITER
@ -395,7 +395,7 @@ static int boxBlurInterp(const uint8_t* src, int src_y_stride, uint8_t* dst,
RIGHT_BORDER_ITER
}
#endif
for (; x < border; x++) {
for (; x < border; ++x) {
RIGHT_BORDER_ITER
}
#undef RIGHT_BORDER_ITER
@ -519,17 +519,19 @@ static void kernel_clamped(uint8_t dst[], int rx, int ry, const uint32_t sum[],
int prev_y = -2*ry;
int next_y = 1;
for (int y = 0; y < dh; y++) {
for (int y = 0; y < dh; ++y) {
int py = SkClampPos(prev_y) * sumStride;
int ny = SkFastMin32(next_y, sh) * sumStride;
int prev_x = -2*rx;
int next_x = 1;
for (int x = 0; x < dw; x++) {
for (int x = 0; x < dw; ++x) {
int px = SkClampPos(prev_x);
int nx = SkFastMin32(next_x, sw);
// TODO: should we be adding 1/2 (1 << 23) to round to the
// nearest integer here?
uint32_t tmp = sum[px+py] + sum[nx+ny] - sum[nx+py] - sum[px+ny];
*dst++ = SkToU8(tmp * scale >> 24);
@ -549,7 +551,7 @@ static void kernel_clamped(uint8_t dst[], int rx, int ry, const uint32_t sum[],
*
* The inner loop is conceptually simple; we break it into several sections
* to improve performance. Here's the original version:
for (int x = 0; x < dw; x++) {
for (int x = 0; x < dw; ++x) {
int px = SkClampPos(prev_x);
int nx = SkFastMin32(next_x, sw);
@ -585,7 +587,7 @@ static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
SkASSERT(2*rx <= dw - 2*rx);
for (int y = 0; y < dh; y++) {
for (int y = 0; y < dh; ++y) {
int py = SkClampPos(prev_y) * sumStride;
int ny = SkFastMin32(next_y, sh) * sumStride;
@ -593,7 +595,7 @@ static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
int next_x = 1;
int x = 0;
for (; x < 2*rx; x++) {
for (; x < 2*rx; ++x) {
SkASSERT(prev_x <= 0);
SkASSERT(next_x <= sw);
@ -631,7 +633,7 @@ static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
}
#endif
for (; x < dw - 2*rx; x++) {
for (; x < dw - 2*rx; ++x) {
SkASSERT(prev_x >= 0);
SkASSERT(next_x <= sw);
@ -642,7 +644,7 @@ static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
next_x += 1;
}
for (; x < dw; x++) {
for (; x < dw; ++x) {
SkASSERT(prev_x >= 0);
SkASSERT(next_x > sw);
@ -666,17 +668,17 @@ static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
* is wider than the source image.
*/
static void kernel_interp_clamped(uint8_t dst[], int rx, int ry,
const uint32_t sum[], int sw, int sh, U8CPU outer_weight) {
const uint32_t sum[], int sw, int sh, U8CPU outerWeight) {
SkASSERT(2*rx > sw);
int inner_weight = 255 - outer_weight;
int innerWeight = 255 - outerWeight;
// round these guys up if they're bigger than 127
outer_weight += outer_weight >> 7;
inner_weight += inner_weight >> 7;
outerWeight += outerWeight >> 7;
innerWeight += innerWeight >> 7;
uint32_t outer_scale = (outer_weight << 16) / ((2*rx + 1)*(2*ry + 1));
uint32_t inner_scale = (inner_weight << 16) / ((2*rx - 1)*(2*ry - 1));
uint32_t outerScale = (outerWeight << 16) / ((2*rx + 1)*(2*ry + 1));
uint32_t innerScale = (innerWeight << 16) / ((2*rx - 1)*(2*ry - 1));
int sumStride = sw + 1;
@ -686,7 +688,7 @@ static void kernel_interp_clamped(uint8_t dst[], int rx, int ry,
int prev_y = -2*ry;
int next_y = 1;
for (int y = 0; y < dh; y++) {
for (int y = 0; y < dh; ++y) {
int py = SkClampPos(prev_y) * sumStride;
int ny = SkFastMin32(next_y, sh) * sumStride;
@ -696,19 +698,19 @@ static void kernel_interp_clamped(uint8_t dst[], int rx, int ry,
int prev_x = -2*rx;
int next_x = 1;
for (int x = 0; x < dw; x++) {
for (int x = 0; x < dw; ++x) {
int px = SkClampPos(prev_x);
int nx = SkFastMin32(next_x, sw);
int ipx = SkClampPos(prev_x + 1);
int inx = SkClampMax(next_x - 1, sw);
uint32_t outer_sum = sum[px+py] + sum[nx+ny]
uint32_t outerSum = sum[px+py] + sum[nx+ny]
- sum[nx+py] - sum[px+ny];
uint32_t inner_sum = sum[ipx+ipy] + sum[inx+iny]
uint32_t innerSum = sum[ipx+ipy] + sum[inx+iny]
- sum[inx+ipy] - sum[ipx+iny];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
prev_x += 1;
next_x += 1;
@ -726,19 +728,19 @@ static void kernel_interp_clamped(uint8_t dst[], int rx, int ry,
*
* The inner loop is conceptually simple; we break it into several variants
* to improve performance. Here's the original version:
for (int x = 0; x < dw; x++) {
for (int x = 0; x < dw; ++x) {
int px = SkClampPos(prev_x);
int nx = SkFastMin32(next_x, sw);
int ipx = SkClampPos(prev_x + 1);
int inx = SkClampMax(next_x - 1, sw);
uint32_t outer_sum = sum[px+py] + sum[nx+ny]
uint32_t outerSum = sum[px+py] + sum[nx+ny]
- sum[nx+py] - sum[px+ny];
uint32_t inner_sum = sum[ipx+ipy] + sum[inx+iny]
uint32_t innerSum = sum[ipx+ipy] + sum[inx+iny]
- sum[inx+ipy] - sum[ipx+iny];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
prev_x += 1;
next_x += 1;
@ -751,23 +753,23 @@ static void kernel_interp_clamped(uint8_t dst[], int rx, int ry,
* speedup.
*/
static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
const uint32_t sum[], int sw, int sh, U8CPU outer_weight) {
const uint32_t sum[], int sw, int sh, U8CPU outerWeight) {
SkASSERT(rx > 0 && ry > 0);
SkASSERT(outer_weight <= 255);
SkASSERT(outerWeight <= 255);
if (2*rx > sw) {
kernel_interp_clamped(dst, rx, ry, sum, sw, sh, outer_weight);
kernel_interp_clamped(dst, rx, ry, sum, sw, sh, outerWeight);
return;
}
int inner_weight = 255 - outer_weight;
int innerWeight = 255 - outerWeight;
// round these guys up if they're bigger than 127
outer_weight += outer_weight >> 7;
inner_weight += inner_weight >> 7;
outerWeight += outerWeight >> 7;
innerWeight += innerWeight >> 7;
uint32_t outer_scale = (outer_weight << 16) / ((2*rx + 1)*(2*ry + 1));
uint32_t inner_scale = (inner_weight << 16) / ((2*rx - 1)*(2*ry - 1));
uint32_t outerScale = (outerWeight << 16) / ((2*rx + 1)*(2*ry + 1));
uint32_t innerScale = (innerWeight << 16) / ((2*rx - 1)*(2*ry - 1));
int sumStride = sw + 1;
@ -779,7 +781,7 @@ static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
SkASSERT(2*rx <= dw - 2*rx);
for (int y = 0; y < dh; y++) {
for (int y = 0; y < dh; ++y) {
int py = SkClampPos(prev_y) * sumStride;
int ny = SkFastMin32(next_y, sh) * sumStride;
@ -790,7 +792,7 @@ static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
int next_x = 1;
int x = 0;
for (; x < 2*rx; x++) {
for (; x < 2*rx; ++x) {
SkASSERT(prev_x < 0);
SkASSERT(next_x <= sw);
@ -800,12 +802,12 @@ static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
int ipx = 0;
int inx = next_x - 1;
uint32_t outer_sum = sum[px+py] + sum[nx+ny]
uint32_t outerSum = sum[px+py] + sum[nx+ny]
- sum[nx+py] - sum[px+ny];
uint32_t inner_sum = sum[ipx+ipy] + sum[inx+iny]
uint32_t innerSum = sum[ipx+ipy] + sum[inx+iny]
- sum[inx+ipy] - sum[ipx+iny];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
prev_x += 1;
next_x += 1;
@ -825,42 +827,42 @@ static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
SkASSERT(prev_x >= 0);
SkASSERT(next_x <= sw);
uint32_t outer_sum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
uint32_t inner_sum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
outer_sum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
inner_sum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
outer_sum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
inner_sum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
outer_sum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
inner_sum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
uint32_t outerSum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
uint32_t innerSum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
outerSum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
innerSum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
outerSum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
innerSum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
outerSum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
innerSum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
prev_x += 4;
next_x += 4;
}
#endif
for (; x < dw - 2*rx; x++) {
for (; x < dw - 2*rx; ++x) {
SkASSERT(prev_x >= 0);
SkASSERT(next_x <= sw);
uint32_t outer_sum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
uint32_t inner_sum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
uint32_t outerSum = sum[i0++] + sum[i1++] - sum[i2++] - sum[i3++];
uint32_t innerSum = sum[i4++] + sum[i5++] - sum[i6++] - sum[i7++];
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
prev_x += 1;
next_x += 1;
}
for (; x < dw; x++) {
for (; x < dw; ++x) {
SkASSERT(prev_x >= 0);
SkASSERT(next_x > sw);
@ -870,12 +872,12 @@ static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
int ipx = prev_x + 1;
int inx = sw;
uint32_t outer_sum = sum[px+py] + sum[nx+ny]
uint32_t outerSum = sum[px+py] + sum[nx+ny]
- sum[nx+py] - sum[px+ny];
uint32_t inner_sum = sum[ipx+ipy] + sum[inx+iny]
uint32_t innerSum = sum[ipx+ipy] + sum[inx+iny]
- sum[inx+ipy] - sum[ipx+iny];
*dst++ = SkToU8((outer_sum * outer_scale
+ inner_sum * inner_scale) >> 24);
*dst++ = SkToU8((outerSum * outerScale
+ innerSum * innerScale) >> 24);
prev_x += 1;
next_x += 1;
@ -955,6 +957,7 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
SkScalar radius, Style style, Quality quality,
SkIPoint* margin, bool separable)
{
if (src.fFormat != SkMask::kA8_Format) {
return false;
}
@ -963,16 +966,27 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
if (radius < SkIntToScalar(3)) {
quality = kLow_Quality;
}
// highQuality: use three box blur passes as a cheap way to approximate a Gaussian blur
// highQuality: use three box blur passes as a cheap way
// to approximate a Gaussian blur
int passCount = (kHigh_Quality == quality) ? 3 : 1;
SkScalar passRadius = (kHigh_Quality == quality) ? SkScalarMul( radius, kBlurRadiusFudgeFactor): radius;
SkScalar passRadius = (kHigh_Quality == quality) ?
SkScalarMul( radius, kBlurRadiusFudgeFactor):
radius;
#ifndef SK_IGNORE_BLUR_RADIUS_CORRECTNESS
// multiply the given radius by sqrt(2)/2 to convert
// from (2x) standard deviation to needed box width
const SkScalar radiusMultiplier = SkFloatToScalar(0.707f);
SkScalar boxWidth = SkScalarMul(passRadius, radiusMultiplier);
passRadius = SkScalarMul(boxWidth,SK_ScalarHalf) - SK_ScalarHalf;
#endif
int rx = SkScalarCeil(passRadius);
int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
int outerWeight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
SkASSERT(rx >= 0);
SkASSERT((unsigned)outer_weight <= 255);
SkASSERT((unsigned)outerWeight <= 255);
if (rx <= 0) {
return false;
}
@ -981,11 +995,13 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
int padx = passCount * rx;
int pady = passCount * ry;
if (margin) {
margin->set(padx, pady);
}
dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
dst->fRowBytes = dst->fBounds.width();
dst->fFormat = SkMask::kA8_Format;
dst->fImage = NULL;
@ -1000,7 +1016,6 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
int sh = src.fBounds.height();
const uint8_t* sp = src.fImage;
uint8_t* dp = SkMask::AllocImage(dstSize);
SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
// build the blurry destination
@ -1008,8 +1023,8 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
uint8_t* tp = tmpBuffer.get();
int w = sw, h = sh;
if (outer_weight == 255) {
if (outerWeight == 255) {
int loRadius, hiRadius;
get_adjusted_radii(passRadius, &loRadius, &hiRadius);
if (kHigh_Quality == quality) {
@ -1028,16 +1043,16 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
} else {
if (kHigh_Quality == quality) {
// Do three X blurs, with a transpose on the final one.
w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, false, outer_weight);
w = boxBlurInterp(tp, w, dp, rx, w, h, false, outer_weight);
w = boxBlurInterp(dp, w, tp, rx, w, h, true, outer_weight);
w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, false, outerWeight);
w = boxBlurInterp(tp, w, dp, rx, w, h, false, outerWeight);
w = boxBlurInterp(dp, w, tp, rx, w, h, true, outerWeight);
// Do three Y blurs, with a transpose on the final one.
h = boxBlurInterp(tp, h, dp, ry, h, w, false, outer_weight);
h = boxBlurInterp(dp, h, tp, ry, h, w, false, outer_weight);
h = boxBlurInterp(tp, h, dp, ry, h, w, true, outer_weight);
h = boxBlurInterp(tp, h, dp, ry, h, w, false, outerWeight);
h = boxBlurInterp(dp, h, tp, ry, h, w, false, outerWeight);
h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
} else {
w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outer_weight);
h = boxBlurInterp(tp, h, dp, ry, h, w, true, outer_weight);
w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outerWeight);
h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
}
}
} else {
@ -1048,10 +1063,10 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
//pass1: sp is source, dp is destination
build_sum_buffer(sumBuffer, sw, sh, sp, src.fRowBytes);
if (outer_weight == 255) {
if (outerWeight == 255) {
apply_kernel(dp, rx, ry, sumBuffer, sw, sh);
} else {
apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outer_weight);
apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outerWeight);
}
if (kHigh_Quality == quality) {
@ -1060,21 +1075,21 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
int tmp_sh = sh + 2 * ry;
SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, dp, tmp_sw);
if (outer_weight == 255)
if (outerWeight == 255)
apply_kernel(tmpBuffer.get(), rx, ry, sumBuffer, tmp_sw, tmp_sh);
else
apply_kernel_interp(tmpBuffer.get(), rx, ry, sumBuffer,
tmp_sw, tmp_sh, outer_weight);
tmp_sw, tmp_sh, outerWeight);
//pass3: tmpBuffer is source, dp is destination
tmp_sw += 2 * rx;
tmp_sh += 2 * ry;
build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, tmpBuffer.get(), tmp_sw);
if (outer_weight == 255)
if (outerWeight == 255)
apply_kernel(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh);
else
apply_kernel_interp(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh,
outer_weight);
outerWeight);
}
}
@ -1126,26 +1141,43 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
quadratic function:
0 x <= -1.5
9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= 1.5
9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= -.5
3/4 - x^2 -.5 < x <= .5
9/8 - 3/2 x + 1/2 x^2 0.5 < x <= 1.5
0 1.5 < x
Mathematica:
g[x_] := Piecewise [ {
{9/8 + 3/2 x + 1/2 x^2 , -1.5 < x <= -.5},
{3/4 - x^2 , -.5 < x <= .5},
{9/8 - 3/2 x + 1/2 x^2 , 0.5 < x <= 1.5}
}, 0]
To get the profile curve of the blurred step function at the rectangle
edge, we evaluate the indefinite integral, which is piecewise cubic:
0 x <= -1.5
5/8 + 9/8 x + 3/4 x^2 + 1/6 x^3 -1.5 < x <= -0.5
9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3 -1.5 < x <= -0.5
1/2 + 3/4 x - 1/3 x^3 -.5 < x <= .5
3/8 + 9/8 x - 3/4 x^2 + 1/6 x^3 .5 < x <= 1.5
7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3 .5 < x <= 1.5
1 1.5 < x
in Mathematica code:
gi[x_] := Piecewise[ {
{ 0 , x <= -1.5 },
{ 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
{ 1/2 + 3/4 x - 1/3 x^3 , -.5 < x <= .5},
{ 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3, .5 < x <= 1.5}
},1]
*/
static float gaussian_integral( float x ) {
if ( x > 1.5f ) {
static float gaussianIntegral(float x) {
if (x > 1.5f) {
return 0.0f;
}
if ( x < -1.5f ) {
if (x < -1.5f) {
return 1.0f;
}
@ -1153,7 +1185,7 @@ static float gaussian_integral( float x ) {
float x3 = x2*x;
if ( x > 0.5f ) {
return 0.5625f - ( x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
}
if ( x > -0.5f ) {
return 0.5f - (0.75f * x - x3 / 3.0f);
@ -1174,19 +1206,19 @@ static float gaussian_integral( float x ) {
memory returned in profile_out.
*/
static int compute_profile( SkScalar radius, unsigned int **profile_out ) {
int size = SkScalarFloorToInt(radius * 3 + 1);
static int compute_profile(SkScalar radius, unsigned int **profile_out) {
int size = SkScalarRoundToInt(radius * 3);
int center = size >> 1;
unsigned int *profile = SkNEW_ARRAY(unsigned int, size);
float invr = 1.0f/radius;
float invr = 1.f/radius;
profile[0] = 255;
for (int x = 1 ; x < size ; x++) {
float scaled_x = ( center - x ) * invr;
float gi = gaussian_integral( scaled_x );
profile[x] = 255 - (uint8_t) ( 255.f * gi );
for (int x = 1 ; x < size ; ++x) {
float scaled_x = (center - x - .5) * invr;
float gi = gaussianIntegral(scaled_x);
profile[x] = 255 - (uint8_t) (255.f * gi);
}
*profile_out = profile;
@ -1200,29 +1232,49 @@ static int compute_profile( SkScalar radius, unsigned int **profile_out ) {
// Implementation adapted from Michael Herf's approach:
// http://stereopsis.com/shadowrect/
static inline unsigned int profile_lookup( unsigned int *profile, int loc, int blurred_width, int sharp_width ) {
int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far are we from the original edge?
int ox = dx >> 1;
if (ox < 0) {
ox = 0;
}
return profile[ox];
}
bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
SkScalar provided_radius, Style style, Quality quality,
SkScalar provided_radius, Style style,
SkIPoint *margin) {
int profile_size;
unsigned int *profile;
float radius = SkScalarToFloat( SkScalarMul( provided_radius, kBlurRadiusFudgeFactor ) );
#ifndef SK_IGNORE_BLUR_RADIUS_CORRECTNESS
float stddev = SkScalarToFloat( radius ) /2.0f;
radius = stddev * 1.414f;
#endif
profile_size = compute_profile( radius, &profile );
SkAutoTDeleteArray<unsigned int> ada(profile);
int pad = (int) (radius * 1.5f + 1);
int pad = profile_size/2;
if (margin) {
margin->set( pad, pad );
}
dst->fBounds = SkIRect::MakeWH(SkScalarFloorToInt(src.width()), SkScalarFloorToInt(src.height()));
dst->fBounds.outset(pad, pad);
int shadow_left = -pad;
int shadow_top = -pad;
int shadow_right = src.width() + pad;
int shadow_bottom = src.height() + pad;
dst->fBounds.set(shadow_left, shadow_top, shadow_right, shadow_bottom);
dst->fRowBytes = dst->fBounds.width();
dst->fFormat = SkMask::kA8_Format;
dst->fImage = NULL;
size_t dstSize = dst->computeImageSize();
if (0 == dstSize) {
return false; // too big to allocate, abort
@ -1235,8 +1287,8 @@ bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
dst->fImage = dp;
int dst_height = dst->fBounds.height();
int dst_width = dst->fBounds.width();
int dstHeight = dst->fBounds.height();
int dstWidth = dst->fBounds.width();
// nearest odd number less than the profile size represents the center
// of the (2x scaled) profile
@ -1246,29 +1298,210 @@ bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
int h = sh - center;
uint8_t *outptr = dp;
SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
for (int y = 0 ; y < dst_height ; y++)
{
// time to fill in a scanline of the blurry rectangle.
// to avoid floating point math, everything is multiplied by
// 2 where needed. This keeps things nice and integer-oriented.
int dy = abs((y << 1) - dst_height) - h; // how far are we from the original edge?
int oy = dy >> 1;
if (oy < 0) oy = 0;
unsigned int profile_y = profile[oy];
for (int x = 0 ; x < (dst_width << 1) ; x += 2) {
int dx = abs( x - dst_width ) - w;
int ox = dx >> 1;
if (ox < 0) ox = 0;
unsigned int maskval = SkMulDiv255Round(profile[ox], profile_y);
for (int x = 0 ; x < dstWidth ; ++x) {
if (profile_size <= sw) {
horizontalScanline[x] = profile_lookup(profile, x, dstWidth, w);
} else {
float span = float(sw)/radius;
float giX = 1.5 - (x+.5)/radius;
horizontalScanline[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
}
}
for (int y = 0 ; y < dstHeight ; ++y) {
unsigned int profile_y;
if (profile_size <= sh) {
profile_y = profile_lookup(profile, y, dstHeight, h);
} else {
float span = float(sh)/radius;
float giY = 1.5 - (y+.5)/radius;
profile_y = (uint8_t) (255 * (gaussianIntegral(giY) - gaussianIntegral(giY + span)));
}
for (int x = 0 ; x < dstWidth ; x++) {
unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], profile_y);
*(outptr++) = maskval;
}
}
if (style == kInner_Style) {
// now we allocate the "real" dst, mirror the size of src
size_t srcSize = src.width() * src.height();
if (0 == srcSize) {
return false; // too big to allocate, abort
}
dst->fImage = SkMask::AllocImage(srcSize);
for (int y = 0 ; y < sh ; y++) {
uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
uint8_t *inner_scanline = dst->fImage + y*sw;
memcpy(inner_scanline, blur_scanline, sw);
}
SkMask::FreeImage(dp);
dst->fBounds.set(0, 0, sw, sh); // restore trimmed bounds
dst->fRowBytes = sw;
} else if (style == kOuter_Style) {
for (int y = pad ; y < dstHeight-pad ; y++) {
uint8_t *dst_scanline = dp + y*dstWidth + pad;
memset(dst_scanline, 0, sw);
}
}
// normal and solid styles are the same for analytic rect blurs, so don't
// need to handle solid specially.
return true;
}
// The "simple" blur is a direct implementation of separable convolution with a discrete
// gaussian kernel. It's "ground truth" in a sense; too slow to be used, but very
// useful for correctness comparisons.
bool SkBlurMask::BlurGroundTruth(SkMask* dst, const SkMask& src, SkScalar provided_radius,
Style style, SkIPoint* margin) {
if (src.fFormat != SkMask::kA8_Format) {
return false;
}
float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudgeFactor));
float stddev = SkScalarToFloat(radius) /2.0f;
float variance = stddev * stddev;
int windowSize = SkScalarCeil(stddev*4);
// round window size up to nearest odd number
windowSize |= 1;
SkAutoTMalloc<float> gaussWindow(windowSize);
int halfWindow = windowSize >> 1;
gaussWindow[halfWindow] = 1;
float windowSum = 1;
for (int x = 1 ; x <= halfWindow ; ++x) {
float gaussian = expf(-x*x / variance);
gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian;
windowSum += 2*gaussian;
}
// leave the filter un-normalized for now; we will divide by the normalization
// sum later;
int pad = halfWindow;
if (margin) {
margin->set( pad, pad );
}
dst->fBounds = src.fBounds;
dst->fBounds.outset(pad, pad);
dst->fRowBytes = dst->fBounds.width();
dst->fFormat = SkMask::kA8_Format;
dst->fImage = NULL;
if (src.fImage) {
size_t dstSize = dst->computeImageSize();
if (0 == dstSize) {
return false; // too big to allocate, abort
}
int srcWidth = src.fBounds.width();
int srcHeight = src.fBounds.height();
int dstWidth = dst->fBounds.width();
const uint8_t* srcPixels = src.fImage;
uint8_t* dstPixels = SkMask::AllocImage(dstSize);
SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dstPixels);
// do the actual blur. First, make a padded copy of the source.
// use double pad so we never have to check if we're outside anything
int padWidth = srcWidth + 4*pad;
int padHeight = srcHeight;
int padSize = padWidth * padHeight;
SkAutoTMalloc<uint8_t> padPixels(padSize);
memset(padPixels, 0, padSize);
for (int y = 0 ; y < srcHeight; ++y) {
uint8_t* padptr = padPixels + y * padWidth + 2*pad;
const uint8_t* srcptr = srcPixels + y * srcWidth;
memcpy(padptr, srcptr, srcWidth);
}
// blur in X, transposing the result into a temporary floating point buffer.
// also double-pad the intermediate result so that the second blur doesn't
// have to do extra conditionals.
int tmpWidth = padHeight + 4*pad;
int tmpHeight = padWidth - 2*pad;
int tmpSize = tmpWidth * tmpHeight;
SkAutoTMalloc<float> tmpImage(tmpSize);
memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0]));
for (int y = 0 ; y < padHeight ; ++y) {
uint8_t *srcScanline = padPixels + y*padWidth;
for (int x = pad ; x < padWidth - pad ; ++x) {
float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output
uint8_t *windowCenter = srcScanline + x;
for (int i = -pad ; i <= pad ; ++i) {
*outPixel += gaussWindow[pad+i]*windowCenter[i];
}
*outPixel /= windowSum;
}
}
// blur in Y; now filling in the actual desired destination. We have to do
// the transpose again; these transposes guarantee that we read memory in
// linear order.
for (int y = 0 ; y < tmpHeight ; ++y) {
float *srcScanline = tmpImage + y*tmpWidth;
for (int x = pad ; x < tmpWidth - pad ; ++x) {
float *windowCenter = srcScanline + x;
float finalValue = 0;
for (int i = -pad ; i <= pad ; ++i) {
finalValue += gaussWindow[pad+i]*windowCenter[i];
}
finalValue /= windowSum;
uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output
int integerPixel = int(finalValue + 0.5f);
*outPixel = SkClampMax( SkClampPos(integerPixel), 255 );
}
}
dst->fImage = dstPixels;
// if need be, alloc the "real" dst (same size as src) and copy/merge
// the blur into it (applying the src)
if (style == kInner_Style) {
// now we allocate the "real" dst, mirror the size of src
size_t srcSize = src.computeImageSize();
if (0 == srcSize) {
return false; // too big to allocate, abort
}
dst->fImage = SkMask::AllocImage(srcSize);
merge_src_with_blur(dst->fImage, src.fRowBytes,
srcPixels, src.fRowBytes,
dstPixels + pad*dst->fRowBytes + pad,
dst->fRowBytes, srcWidth, srcHeight);
SkMask::FreeImage(dstPixels);
} else if (style != kNormal_Style) {
clamp_with_orig(dstPixels + pad*dst->fRowBytes + pad,
dst->fRowBytes, srcPixels, src.fRowBytes, srcWidth, srcHeight, style);
}
(void)autoCall.detach();
}
if (style == kInner_Style) {
dst->fBounds = src.fBounds; // restore trimmed bounds
dst->fRowBytes = src.fRowBytes;
}
return true;
}

View File

@ -29,15 +29,23 @@ public:
};
static bool BlurRect(SkMask *dst, const SkRect &src,
SkScalar radius, Style style, Quality quality,
SkScalar radius, Style style,
SkIPoint *margin = NULL);
static bool Blur(SkMask* dst, const SkMask& src,
SkScalar radius, Style style, Quality quality,
SkIPoint* margin = NULL);
static bool BlurSeparable(SkMask* dst, const SkMask& src,
SkScalar radius, Style style, Quality quality,
SkIPoint* margin = NULL);
// the "ground truth" blur does a gaussian convolution; it's slow
// but useful for comparison purposes.
static bool BlurGroundTruth(SkMask* dst, const SkMask& src,
SkScalar provided_radius, Style style,
SkIPoint* margin = NULL);
private:
static bool Blur(SkMask* dst, const SkMask& src,
SkScalar radius, Style style, Quality quality,