Index: src/effects/GrCircleBlurFragmentProcessor.cpp |
diff --git a/src/effects/GrCircleBlurFragmentProcessor.cpp b/src/effects/GrCircleBlurFragmentProcessor.cpp |
index e9c84ec4ed54a9f6cbd53edc6758bc3955ce1f13..7718e43f7bb0e5775fa5bebf31c4b5c5d01083b4 100644 |
--- a/src/effects/GrCircleBlurFragmentProcessor.cpp |
+++ b/src/effects/GrCircleBlurFragmentProcessor.cpp |
@@ -115,9 +115,9 @@ void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* |
// Evaluate an AA circle function centered at the origin with 'radius' at (x,y) |
static inline float disk(float x, float y, float radius) { |
float distSq = x*x + y*y; |
- if (distSq <= (radius-0.5f)*(radius-0.5f)) { |
+ if (distSq <= (radius - 0.5f) * (radius - 0.5f)) { |
return 1.0f; |
- } else if (distSq >= (radius+0.5f)*(radius+0.5f)) { |
+ } else if (distSq >= (radius + 0.5f) * (radius + 0.5f)) { |
return 0.0f; |
} else { |
float ramp = radius + 0.5f - sqrtf(distSq); |
@@ -130,48 +130,65 @@ static inline float disk(float x, float y, float radius) { |
static void make_half_kernel(float* kernel, int kernelWH, float sigma) { |
SkASSERT(!(kernelWH & 1)); |
- const float kernelOff = (kernelWH-1)/2.0f; |
+ // We treat each cell in the half-kernel as a 1x1 window and evaluate it |
+ // at the center. So the evaluations go from -kernelOff to kernelOff in x |
+ // and -kernelOff to -.5 in y (since this is a top-half kernel). |
+ const float kernelOff = (kernelWH - 1) / 2.0f; |
float b = 1.0f / (2.0f * sigma * sigma); |
// omit the scale term since we're just going to renormalize |
float tot = 0.0f; |
- for (int y = 0; y < kernelWH/2; ++y) { |
- for (int x = 0; x < kernelWH/2; ++x) { |
+ for (int y = 0; y < kernelWH / 2; ++y) { |
+ for (int x = 0; x < kernelWH / 2; ++x) { |
// TODO: use a cheap approximation of the 2D Guassian? |
- float x2 = (x-kernelOff) * (x-kernelOff); |
- float y2 = (y-kernelOff) * (y-kernelOff); |
+ float x2 = (x - kernelOff) * (x - kernelOff); |
+ float y2 = (y - kernelOff) * (y - kernelOff); |
// The kernel is symmetric so only compute it once for both sides |
- kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = expf(-(x2 + y2) * b); |
- tot += 2.0f * kernel[y*kernelWH+x]; |
+ float value = expf(-(x2 + y2) * b); |
+ kernel[y * kernelWH + x] = value; |
+ kernel[y * kernelWH + (kernelWH - x - 1)] = value; |
+ tot += 2.0f * value; |
} |
} |
- // Still normalize the half kernel to 1.0 (rather than 0.5) so we don't |
- // have to scale by 2.0 after convolution. |
- for (int y = 0; y < kernelWH/2; ++y) { |
+ // Normalize the half kernel to 1.0 (rather than 0.5) so we don't have to scale by 2.0 after |
+ // convolution. |
+ for (int y = 0; y < kernelWH / 2; ++y) { |
for (int x = 0; x < kernelWH; ++x) { |
- kernel[y*kernelWH+x] /= tot; |
+ kernel[y * kernelWH + x] /= tot; |
} |
} |
} |
// Apply the half-kernel at 't' away from the center of the circle |
-static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH) { |
+static uint8_t eval_at(float t, float circleR, float* halfKernel, int kernelWH) { |
SkASSERT(!(kernelWH & 1)); |
- const float kernelOff = (kernelWH-1)/2.0f; |
- |
float acc = 0; |
- for (int y = 0; y < kernelWH/2; ++y) { |
- if (kernelOff-y > halfWidth+0.5f) { |
- // All disk() samples in this row will be 0.0f |
+ // We evaluate the kernel application at (x=t, y=0) using halfKernel which represents the top |
+ // half of a 2D Guassian kernel. The full kernel is symmetric so evaluating just the upper half |
+ // is sufficient. The half kernel has been normalized to 1 rather than 0.5 so there is no need |
+ // to double after evaluation. |
+ |
+ // The sample positions relative to (t, 0) match the sampling used to create the half kernel. |
+ const float kernelOff = (kernelWH - 1) / 2.0f; |
+ |
+ for (int j = 0; j < kernelWH / 2; ++j) { |
+ float y = (kernelOff - j); |
+ if (y > circleR + 0.5f) { |
+ // The entire row is above the circle. |
continue; |
} |
- for (int x = 0; x < kernelWH; ++x) { |
- float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth); |
- float kernel = halfKernel[y*kernelWH+x]; |
+ for (int i = 0; i < kernelWH; ++i) { |
+ float x = t - kernelOff + i; |
+ if (x > circleR + 0.5f) { |
+ // Stop evaluation once x crosses outside the circle. |
+ break; |
+ } |
+ float image = disk(x, y, circleR); |
+ float kernel = halfKernel[j * kernelWH + i]; |
acc += kernel * image; |
} |
} |
@@ -179,42 +196,42 @@ static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH |
return SkUnitScalarClampToByte(acc); |
} |
-static inline void compute_profile_offset_and_size(float halfWH, float sigma, |
+static inline void compute_profile_offset_and_size(float circleR, float sigma, |
float* offset, int* size) { |
- if (3*sigma <= halfWH) { |
+ if (3*sigma <= circleR) { |
// The circle is bigger than the Gaussian. In this case we know the interior of the |
// blurred circle is solid. |
- *offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weights texture. |
- // It should always be 255. |
+ *offset = circleR - 3 * sigma; // This location maps to 0.5f in the weights texture. |
+ // It should always be 255. |
*size = SkScalarCeilToInt(6*sigma); |
} else { |
// The Gaussian is bigger than the circle. |
*offset = 0.0f; |
- *size = SkScalarCeilToInt(halfWH + 3*sigma); |
+ *size = SkScalarCeilToInt(circleR + 3*sigma); |
} |
} |
-static uint8_t* create_profile(float halfWH, float sigma) { |
+static uint8_t* create_profile(float circleR, float sigma) { |
int kernelWH = SkScalarCeilToInt(6.0f*sigma); |
kernelWH = (kernelWH + 1) & ~1; // make it the next even number up |
- SkAutoTArray<float> halfKernel(kernelWH*kernelWH/2); |
+ SkAutoTArray<float> halfKernel(kernelWH * kernelWH / 2); |
make_half_kernel(halfKernel.get(), kernelWH, sigma); |
float offset; |
int numSteps; |
- compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps); |
+ compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps); |
uint8_t* weights = new uint8_t[numSteps]; |
for (int i = 0; i < numSteps - 1; ++i) { |
- weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH); |
+ weights[i] = eval_at(offset+i, circleR, halfKernel.get(), kernelWH); |
} |
// Ensure the tail of the Gaussian goes to zero. |
- weights[numSteps-1] = 0; |
+ weights[numSteps - 1] = 0; |
return weights; |
} |
@@ -224,15 +241,7 @@ GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( |
const SkRect& circle, |
float sigma, |
float* offset) { |
- float halfWH = circle.width() / 2.0f; |
- |
- int size; |
- compute_profile_offset_and_size(halfWH, sigma, offset, &size); |
- |
- GrSurfaceDesc texDesc; |
- texDesc.fWidth = size; |
- texDesc.fHeight = 1; |
- texDesc.fConfig = kAlpha_8_GrPixelConfig; |
+ float circleR = circle.width() / 2.0f; |
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); |
GrUniqueKey key; |
@@ -240,13 +249,22 @@ GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( |
// The profile curve varies with both the sigma of the Gaussian and the size of the |
// disk. Quantizing to 16.16 should be close enough though. |
builder[0] = SkScalarToFixed(sigma); |
- builder[1] = SkScalarToFixed(halfWH); |
+ builder[1] = SkScalarToFixed(circleR); |
builder.finish(); |
GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); |
+ int profileSize; |
+ compute_profile_offset_and_size(circleR, sigma, offset, &profileSize); |
+ |
if (!blurProfile) { |
- SkAutoTDeleteArray<uint8_t> profile(create_profile(halfWH, sigma)); |
+ |
+ GrSurfaceDesc texDesc; |
+ texDesc.fWidth = profileSize; |
+ texDesc.fHeight = 1; |
+ texDesc.fConfig = kAlpha_8_GrPixelConfig; |
+ |
+ SkAutoTDeleteArray<uint8_t> profile(create_profile(circleR, sigma)); |
blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes, profile.get(), 0); |
if (blurProfile) { |