aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/effects/SkBlurMask.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/effects/SkBlurMask.cpp')
-rw-r--r--src/effects/SkBlurMask.cpp162
1 files changed, 161 insertions, 1 deletions
diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp
index 658b0fd47e..766da539fe 100644
--- a/src/effects/SkBlurMask.cpp
+++ b/src/effects/SkBlurMask.cpp
@@ -12,6 +12,14 @@
#include "SkTemplates.h"
#include "SkEndian.h"
+// scale factor for the blur radius to match the behavior of the all existing blur
+// code (both on the CPU and the GPU). This magic constant is 1/sqrt(3).
+
+// TODO: get rid of this fudge factor and move any required fudging up into
+// the calling library
+
+#define kBlurRadiusFudgeFactor SkFloatToScalar( .57735f )
+
#define UNROLL_SEPARABLE_LOOPS
/**
@@ -866,7 +874,7 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
// highQuality: use three box blur passes as a cheap way to approximate a Gaussian blur
int passCount = (kHigh_Quality == quality) ? 3 : 1;
- SkScalar passRadius = SkScalarDiv(radius, SkScalarSqrt(SkIntToScalar(passCount)));
+ SkScalar passRadius = (kHigh_Quality == quality) ? SkScalarMul( radius, kBlurRadiusFudgeFactor): radius;
int rx = SkScalarCeil(passRadius);
int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
@@ -1021,3 +1029,155 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
{
return SkBlurMask::Blur(dst, src, radius, style, quality, margin, false);
}
+
+/* Convolving a box with itself three times results in a piecewise
+ quadratic function:
+
+ 0 x <= -1.5
+ 9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= 1.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
+
+ 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
+ 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
+ 1 1.5 < x
+*/
+
+static float gaussian_integral( float x ) {
+ if ( x > 1.5f ) {
+ return 0.0f;
+ }
+ if ( x < -1.5f ) {
+ return 1.0f;
+ }
+
+ float x2 = x*x;
+ float x3 = x2*x;
+
+ if ( x > 0.5 ) {
+ return .5625 - ( x3 / 6 - 3 * x2 / 4 + 1.125 * x);
+ }
+ if ( x > -0.5 ) {
+ return 0.5 - (0.75 * x - x3 / 3);
+ }
+ return 0.4375 + (-x3 / 6 - 3 * x2 / 4 - 1.125 * x);
+}
+
+/*
+ compute_profile allocates and fills in an array of floating
+ point values between 0 and 255 for the profile signature of
+ a blurred half-plane with the given blur radius. Since we're
+ going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
+ all the time, we actually fill in the profile pre-inverted
+ (already done 255-x).
+
+ The function returns the size of the array allocated for the
+ profile. It's the responsibility of the caller to delete the
+ memory returned in profile_out.
+*/
+
+static int compute_profile( SkScalar radius, unsigned int **profile_out ) {
+ int size = radius * 3 + 1;
+ int center = size >> 1;
+
+ unsigned int *profile = new unsigned int [size];
+
+ float invr = 1.0f/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 );
+ }
+
+ *profile_out = profile;
+ return size;
+}
+
+// TODO MAYBE: Maintain a profile cache to avoid recomputing this for
+// commonly used radii. Consider baking some of the most common blur radii
+// directly in as static data?
+
+// Implementation adapted from Michael Herf's approach:
+// http://stereopsis.com/shadowrect/
+
+bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
+ SkScalar provided_radius, Style style, Quality quality,
+ SkIPoint *margin) {
+ int profile_size;
+ unsigned int *profile;
+
+
+ float radius = SkScalarToFloat( SkScalarMul( provided_radius, kBlurRadiusFudgeFactor ) );
+
+ profile_size = compute_profile( radius, &profile );
+
+ int pad = (int) (radius * 1.5f + 1);
+ if (margin) {
+ margin->set( pad, pad );
+ }
+ dst->fBounds = SkIRect::MakeWH(src.width(), src.height());
+ dst->fBounds.outset(pad, pad);
+
+ 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
+ }
+
+ int sw = src.width();
+ int sh = src.height();
+
+ uint8_t* dp = SkMask::AllocImage(dstSize);
+
+ dst->fImage = dp;
+
+ SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
+
+ int dst_height = dst->fBounds.height();
+ int dst_width = dst->fBounds.width();
+
+ // nearest odd number less than the profile size represents the center
+ // of the (2x scaled) profile
+ int center = ( profile_size & ~1 ) - 1;
+
+ int w = sw - center;
+ int h = sh - center;
+
+ uint8_t *outptr = dp;
+
+ 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);
+
+ *(outptr++) = maskval;
+ }
+ }
+
+ return true;
+}