From a99a92cebaa46cf792cf86eaad1a4c3f9d6162f7 Mon Sep 17 00:00:00 2001 From: "humper@google.com" Date: Wed, 20 Feb 2013 16:42:06 +0000 Subject: [PATCH] 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 --- bench/BlurRectBench.cpp | 115 +++++++++-- gm/blurrect.cpp | 243 ++++++++++++++++++----- src/effects/SkBlurMask.cpp | 485 +++++++++++++++++++++++++++++++++------------ src/effects/SkBlurMask.h | 12 +- 4 files changed, 660 insertions(+), 195 deletions(-) diff --git a/bench/BlurRectBench.cpp b/bench/BlurRectBench.cpp index a3825be..cbb624c 100644 --- a/bench/BlurRectBench.cpp +++ b/bench/BlurRectBench.cpp @@ -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; +}; + +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 BlurRectSeparableBench(p, SMALL);) -DEF_BENCH(return new BlurRectSeparableBench(p, BIG);) -DEF_BENCH(return new BlurRectSeparableBench(p, REALBIG);) -DEF_BENCH(return new BlurRectSeparableBench(p, REAL);) +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 diff --git a/gm/blurrect.cpp b/gm/blurrect.cpp index a5e8cf0..52f177d 100644 --- a/gm/blurrect.cpp +++ b/gm/blurrect.cpp @@ -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 fMaskFilter; - SkString fName; - PaintProc fPProc; - SkAlpha fAlpha; + SkAutoTUnref 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)); + + SkISize canvas_size = canvas->getDeviceSize(); + int center_x = (canvas_size.fWidth - r.width())/2; + int center_y = (canvas_size.fHeight - r.height())/2; + + SkMask mask; + + if (!this->makeMask(&mask, r)) { + SkPaint paint; + r.offset( center_x, center_y ); + canvas->drawRect(r,paint); + return; + } + SkAutoMaskFreeImage amfi(mask.fImage); - SkMask mask; + SkBitmap bm; + bm.setConfig(SkBitmap::kA8_Config, mask.fBounds.width(), mask.fBounds.height()); + bm.setPixels(mask.fImage); - this->makeMask(&mask, r); - SkAutoMaskFreeImage amfi(mask.fImage); + center_x = (canvas_size.fWidth - mask.fBounds.width())/2; + center_y = (canvas_size.fHeight - mask.fBounds.height())/2; - SkBitmap bm; - bm.setConfig(SkBitmap::kA8_Config, mask.fBounds.width(), mask.fBounds.height()); - bm.setPixels(mask.fImage); - canvas->drawBitmap(bm, 50, 50, NULL); + 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);) - -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);) +// regular size rects, blurs should be small enough not to completely overlap. + +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 diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp index 70efa0b..e2dfd2b 100644 --- a/src/effects/SkBlurMask.cpp +++ b/src/effects/SkBlurMask.cpp @@ -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 autoCall(dp); // build the blurry destination @@ -1008,8 +1023,8 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src, SkAutoTMalloc 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 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 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,28 +1298,209 @@ bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src, int h = sh - center; uint8_t *outptr = dp; + + SkAutoTMalloc 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. + 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))); + } - 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; + 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. - unsigned int profile_y = profile[oy]; + return true; +} - 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; +// 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. - unsigned int maskval = SkMulDiv255Round(profile[ox], profile_y); +bool SkBlurMask::BlurGroundTruth(SkMask* dst, const SkMask& src, SkScalar provided_radius, + Style style, SkIPoint* margin) { + + if (src.fFormat != SkMask::kA8_Format) { + return false; + } - *(outptr++) = maskval; + 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 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 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 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 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; diff --git a/src/effects/SkBlurMask.h b/src/effects/SkBlurMask.h index 853ba52..51ec8f8 100644 --- a/src/effects/SkBlurMask.h +++ b/src/effects/SkBlurMask.h @@ -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, -- 2.7.4