Index: src/effects/GrCircleBlurFragmentProcessor.cpp |
diff --git a/src/effects/GrCircleBlurFragmentProcessor.cpp b/src/effects/GrCircleBlurFragmentProcessor.cpp |
index 7718e43f7bb0e5775fa5bebf31c4b5c5d01083b4..ff6985f7c3d56b5ea13b836c786acd9a4dd5a73e 100644 |
--- a/src/effects/GrCircleBlurFragmentProcessor.cpp |
+++ b/src/effects/GrCircleBlurFragmentProcessor.cpp |
@@ -112,93 +112,81 @@ void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* |
inout->mulByUnknownSingleComponent(); |
} |
-// 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)) { |
- return 1.0f; |
- } else if (distSq >= (radius + 0.5f) * (radius + 0.5f)) { |
- return 0.0f; |
- } else { |
- float ramp = radius + 0.5f - sqrtf(distSq); |
- SkASSERT(ramp >= 0.0f && ramp <= 1.0f); |
- return ramp; |
- } |
-} |
- |
-// Create the top half of an even-sized Gaussian kernel |
-static void make_half_kernel(float* kernel, int kernelWH, float sigma) { |
- SkASSERT(!(kernelWH & 1)); |
- |
- // 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 |
- |
+// Create a Gaussian half-kernel and a summed area table given a sigma and number of discrete |
+// steps. The half kernel is normalized to sum to 0.5. |
+static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel, |
+ int halfKernelSize, float sigma) { |
+ const float invSigma = 1.f / sigma; |
+ const float b = -0.5f * invSigma * invSigma; |
float tot = 0.0f; |
- 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); |
- // The kernel is symmetric so only compute it once for both sides |
- float value = expf(-(x2 + y2) * b); |
- kernel[y * kernelWH + x] = value; |
- kernel[y * kernelWH + (kernelWH - x - 1)] = value; |
- tot += 2.0f * value; |
- } |
+ // Compute half kernel values at half pixel steps out from the center. |
+ float t = 0.5f; |
+ for (int i = 0; i < halfKernelSize; ++i) { |
+ float value = expf(t * t * b); |
+ tot += value; |
+ halfKernel[i] = value; |
+ t += 1.f; |
} |
- // 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; |
- } |
+ float sum = 0.f; |
+ // The half kernel should sum to 0.5 not 1.0. |
+ tot *= 2.f; |
+ for (int i = 0; i < halfKernelSize; ++i) { |
+ halfKernel[i] /= tot; |
+ sum += halfKernel[i]; |
+ summedHalfKernel[i] = sum; |
} |
} |
-// Apply the half-kernel at 't' away from the center of the circle |
-static uint8_t eval_at(float t, float circleR, float* halfKernel, int kernelWH) { |
- SkASSERT(!(kernelWH & 1)); |
+// Applies the 1D half kernel vertically at a point (x, 0) to a circle centered at the origin with |
+// radius circleR. |
+static float eval_vertically(float x, float circleR, const float* summedHalfKernelTable, |
+ int halfKernelSize) { |
+ // Given x find the positive y that is on the edge of the circle. |
+ float y = sqrtf(fabs(circleR * circleR - x * x)); |
+ // In the column at x we exit the circle at +y and -y |
+ // table entry j is actually the kernel evaluated at j + 0.5. |
+ y -= 0.5f; |
+ int yInt = SkScalarFloorToInt(y); |
+ SkASSERT(yInt >= -1); |
+ if (y < 0) { |
+ return (y + 0.5f) * summedHalfKernelTable[0]; |
+ } else if (yInt >= halfKernelSize - 1) { |
+ return 0.5f; |
+ } else { |
+ float yFrac = y - yInt; |
+ return (1.f - yFrac) * summedHalfKernelTable[yInt] + |
+ yFrac * summedHalfKernelTable[yInt + 1]; |
+ } |
+} |
+// Apply the kernel at point (t, 0) to a circle centered at the origin with radius circleR. |
+static uint8_t eval_at(float t, float circleR, const float* halfKernel, |
+ const float* summedHalfKernelTable, int halfKernelSize) { |
float acc = 0; |
- // 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. |
+ for (int i = 0; i < halfKernelSize; ++i) { |
+ float x = t - i - 0.5f; |
+ if (x < -circleR || x > circleR) { |
continue; |
} |
- |
- 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; |
+ float verticalEval = eval_vertically(x, circleR, summedHalfKernelTable, halfKernelSize); |
+ acc += verticalEval * halfKernel[i]; |
+ } |
+ for (int i = 0; i < halfKernelSize; ++i) { |
+ float x = t + i + 0.5f; |
+ if (x < -circleR || x > circleR) { |
+ continue; |
} |
+ float verticalEval = eval_vertically(x, circleR, summedHalfKernelTable, halfKernelSize); |
+ acc += verticalEval * halfKernel[i]; |
} |
- |
- return SkUnitScalarClampToByte(acc); |
+ // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about the |
+ // x axis). |
+ return SkUnitScalarClampToByte(2.f * acc); |
} |
static inline void compute_profile_offset_and_size(float circleR, float sigma, |
float* offset, int* size) { |
- |
if (3*sigma <= circleR) { |
// The circle is bigger than the Gaussian. In this case we know the interior of the |
// blurred circle is solid. |
@@ -212,27 +200,33 @@ static inline void compute_profile_offset_and_size(float circleR, float sigma, |
} |
} |
+// This function creates a profile of a blurred circle. It does this by computing a kernel for |
+// half the Gaussian and a matching summed area table. To compute a profile value at x = r it steps |
+// outward in x from (r, 0) in both directions. There is a step for each direction for each entry |
+// in the half kernel. The y contribution at each step is computed from the summed area table using |
+// the height of the circle above the step point. Each y contribution is multiplied by the half |
+// kernel value corresponding to the step in x. |
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); |
- |
- make_half_kernel(halfKernel.get(), kernelWH, sigma); |
- |
float offset; |
int numSteps; |
- |
compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps); |
uint8_t* weights = new uint8_t[numSteps]; |
+ |
+ // The full kernel is 6 sigmas wide. |
+ int halfKernelSize = SkScalarCeilToInt(6.0f*sigma); |
+ // round up to next multiple of 2 and then divide by 2 |
+ halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1; |
+ SkAutoTArray<float> halfKernel(halfKernelSize); |
+ SkAutoTArray<float> summedKernel(halfKernelSize); |
+ make_half_kernel_and_summed_table(halfKernel.get(), summedKernel.get(), halfKernelSize, |
+ sigma); |
for (int i = 0; i < numSteps - 1; ++i) { |
- weights[i] = eval_at(offset+i, circleR, halfKernel.get(), kernelWH); |
+ weights[i] = eval_at(offset+i, circleR, halfKernel.get(), summedKernel.get(), |
+ halfKernelSize); |
} |
// Ensure the tail of the Gaussian goes to zero. |
weights[numSteps - 1] = 0; |
- |
return weights; |
} |