Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(396)

Side by Side Diff: src/effects/GrCircleBlurFragmentProcessor.cpp

Issue 1996653002: Make circle blur profile computation separable (Closed) Base URL: https://chromium.googlesource.com/skia.git@master
Patch Set: fix double to float warning on msvs Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright 2015 Google Inc. 2 * Copyright 2015 Google Inc.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license that can be 4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file. 5 * found in the LICENSE file.
6 */ 6 */
7 7
8 #include "GrCircleBlurFragmentProcessor.h" 8 #include "GrCircleBlurFragmentProcessor.h"
9 9
10 #if SK_SUPPORT_GPU 10 #if SK_SUPPORT_GPU
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
105 105
106 void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrGLSLCaps& caps , 106 void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrGLSLCaps& caps ,
107 GrProcessorKeyBuilder* b) const { 107 GrProcessorKeyBuilder* b) const {
108 GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b); 108 GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b);
109 } 109 }
110 110
111 void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const { 111 void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const {
112 inout->mulByUnknownSingleComponent(); 112 inout->mulByUnknownSingleComponent();
113 } 113 }
114 114
115 // Evaluate an AA circle function centered at the origin with 'radius' at (x,y) 115 // Create a Gaussian half-kernel and a summed area table given a sigma and numbe r of discrete
116 static inline float disk(float x, float y, float radius) { 116 // steps. The half kernel is normalized to sum to 0.5.
117 float distSq = x*x + y*y; 117 static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHa lfKernel,
118 if (distSq <= (radius - 0.5f) * (radius - 0.5f)) { 118 int halfKernelSize, float sigma) {
119 return 1.0f; 119 const float invSigma = 1.f / sigma;
120 } else if (distSq >= (radius + 0.5f) * (radius + 0.5f)) { 120 const float b = -0.5f * invSigma * invSigma;
121 return 0.0f; 121 float tot = 0.0f;
122 } else { 122 // Compute half kernel values at half pixel steps out from the center.
123 float ramp = radius + 0.5f - sqrtf(distSq); 123 float t = 0.5f;
124 SkASSERT(ramp >= 0.0f && ramp <= 1.0f); 124 for (int i = 0; i < halfKernelSize; ++i) {
125 return ramp; 125 float value = expf(t * t * b);
126 tot += value;
127 halfKernel[i] = value;
128 t += 1.f;
129 }
130 float sum = 0.f;
131 // The half kernel should sum to 0.5 not 1.0.
132 tot *= 2.f;
133 for (int i = 0; i < halfKernelSize; ++i) {
134 halfKernel[i] /= tot;
135 sum += halfKernel[i];
136 summedHalfKernel[i] = sum;
126 } 137 }
127 } 138 }
128 139
129 // Create the top half of an even-sized Gaussian kernel 140 // Applies the 1D half kernel vertically at a point (x, 0) to a circle centered at the origin with
130 static void make_half_kernel(float* kernel, int kernelWH, float sigma) { 141 // radius circleR.
131 SkASSERT(!(kernelWH & 1)); 142 static float eval_vertically(float x, float circleR, const float* summedHalfKern elTable,
132 143 int halfKernelSize) {
133 // We treat each cell in the half-kernel as a 1x1 window and evaluate it 144 // Given x find the positive y that is on the edge of the circle.
134 // at the center. So the evaluations go from -kernelOff to kernelOff in x 145 float y = sqrtf(fabs(circleR * circleR - x * x));
135 // and -kernelOff to -.5 in y (since this is a top-half kernel). 146 // In the column at x we exit the circle at +y and -y
136 const float kernelOff = (kernelWH - 1) / 2.0f; 147 // table entry j is actually the kernel evaluated at j + 0.5.
137 148 y -= 0.5f;
138 float b = 1.0f / (2.0f * sigma * sigma); 149 int yInt = SkScalarFloorToInt(y);
139 // omit the scale term since we're just going to renormalize 150 SkASSERT(yInt >= -1);
140 151 if (y < 0) {
141 float tot = 0.0f; 152 return (y + 0.5f) * summedHalfKernelTable[0];
142 for (int y = 0; y < kernelWH / 2; ++y) { 153 } else if (yInt >= halfKernelSize - 1) {
143 for (int x = 0; x < kernelWH / 2; ++x) { 154 return 0.5f;
144 // TODO: use a cheap approximation of the 2D Guassian? 155 } else {
145 float x2 = (x - kernelOff) * (x - kernelOff); 156 float yFrac = y - yInt;
146 float y2 = (y - kernelOff) * (y - kernelOff); 157 return (1.f - yFrac) * summedHalfKernelTable[yInt] +
147 // The kernel is symmetric so only compute it once for both sides 158 yFrac * summedHalfKernelTable[yInt + 1];
148 float value = expf(-(x2 + y2) * b);
149 kernel[y * kernelWH + x] = value;
150 kernel[y * kernelWH + (kernelWH - x - 1)] = value;
151 tot += 2.0f * value;
152 }
153 }
154 // Normalize the half kernel to 1.0 (rather than 0.5) so we don't have to sc ale by 2.0 after
155 // convolution.
156 for (int y = 0; y < kernelWH / 2; ++y) {
157 for (int x = 0; x < kernelWH; ++x) {
158 kernel[y * kernelWH + x] /= tot;
159 }
160 } 159 }
161 } 160 }
162 161
163 // Apply the half-kernel at 't' away from the center of the circle 162 // Apply the kernel at point (t, 0) to a circle centered at the origin with radi us circleR.
164 static uint8_t eval_at(float t, float circleR, float* halfKernel, int kernelWH) { 163 static uint8_t eval_at(float t, float circleR, const float* halfKernel,
165 SkASSERT(!(kernelWH & 1)); 164 const float* summedHalfKernelTable, int halfKernelSize) {
166
167 float acc = 0; 165 float acc = 0;
168 166
169 // We evaluate the kernel application at (x=t, y=0) using halfKernel which r epresents the top 167 for (int i = 0; i < halfKernelSize; ++i) {
170 // half of a 2D Guassian kernel. The full kernel is symmetric so evaluating just the upper half 168 float x = t - i - 0.5f;
171 // is sufficient. The half kernel has been normalized to 1 rather than 0.5 s o there is no need 169 if (x < -circleR || x > circleR) {
172 // to double after evaluation.
173
174 // The sample positions relative to (t, 0) match the sampling used to create the half kernel.
175 const float kernelOff = (kernelWH - 1) / 2.0f;
176
177 for (int j = 0; j < kernelWH / 2; ++j) {
178 float y = (kernelOff - j);
179 if (y > circleR + 0.5f) {
180 // The entire row is above the circle.
181 continue; 170 continue;
182 } 171 }
183 172 float verticalEval = eval_vertically(x, circleR, summedHalfKernelTable, halfKernelSize);
184 for (int i = 0; i < kernelWH; ++i) { 173 acc += verticalEval * halfKernel[i];
185 float x = t - kernelOff + i; 174 }
186 if (x > circleR + 0.5f) { 175 for (int i = 0; i < halfKernelSize; ++i) {
187 // Stop evaluation once x crosses outside the circle. 176 float x = t + i + 0.5f;
188 break; 177 if (x < -circleR || x > circleR) {
189 } 178 continue;
190 float image = disk(x, y, circleR);
191 float kernel = halfKernel[j * kernelWH + i];
192 acc += kernel * image;
193 } 179 }
180 float verticalEval = eval_vertically(x, circleR, summedHalfKernelTable, halfKernelSize);
181 acc += verticalEval * halfKernel[i];
194 } 182 }
195 183 // Since we applied a half kernel in y we multiply acc by 2 (the circle is s ymmetric about the
196 return SkUnitScalarClampToByte(acc); 184 // x axis).
185 return SkUnitScalarClampToByte(2.f * acc);
197 } 186 }
198 187
199 static inline void compute_profile_offset_and_size(float circleR, float sigma, 188 static inline void compute_profile_offset_and_size(float circleR, float sigma,
200 float* offset, int* size) { 189 float* offset, int* size) {
201
202 if (3*sigma <= circleR) { 190 if (3*sigma <= circleR) {
203 // The circle is bigger than the Gaussian. In this case we know the inte rior of the 191 // The circle is bigger than the Gaussian. In this case we know the inte rior of the
204 // blurred circle is solid. 192 // blurred circle is solid.
205 *offset = circleR - 3 * sigma; // This location maps to 0.5f in the weig hts texture. 193 *offset = circleR - 3 * sigma; // This location maps to 0.5f in the weig hts texture.
206 // It should always be 255. 194 // It should always be 255.
207 *size = SkScalarCeilToInt(6*sigma); 195 *size = SkScalarCeilToInt(6*sigma);
208 } else { 196 } else {
209 // The Gaussian is bigger than the circle. 197 // The Gaussian is bigger than the circle.
210 *offset = 0.0f; 198 *offset = 0.0f;
211 *size = SkScalarCeilToInt(circleR + 3*sigma); 199 *size = SkScalarCeilToInt(circleR + 3*sigma);
212 } 200 }
213 } 201 }
214 202
203 // This function creates a profile of a blurred circle. It does this by computin g a kernel for
204 // half the Gaussian and a matching summed area table. To compute a profile valu e at x = r it steps
205 // outward in x from (r, 0) in both directions. There is a step for each directi on for each entry
206 // in the half kernel. The y contribution at each step is computed from the summ ed area table using
207 // the height of the circle above the step point. Each y contribution is multipl ied by the half
208 // kernel value corresponding to the step in x.
215 static uint8_t* create_profile(float circleR, float sigma) { 209 static uint8_t* create_profile(float circleR, float sigma) {
216
217 int kernelWH = SkScalarCeilToInt(6.0f*sigma);
218 kernelWH = (kernelWH + 1) & ~1; // make it the next even number up
219
220 SkAutoTArray<float> halfKernel(kernelWH * kernelWH / 2);
221
222 make_half_kernel(halfKernel.get(), kernelWH, sigma);
223
224 float offset; 210 float offset;
225 int numSteps; 211 int numSteps;
226
227 compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps); 212 compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps);
228 213
229 uint8_t* weights = new uint8_t[numSteps]; 214 uint8_t* weights = new uint8_t[numSteps];
215
216 // The full kernel is 6 sigmas wide.
217 int halfKernelSize = SkScalarCeilToInt(6.0f*sigma);
218 // round up to next multiple of 2 and then divide by 2
219 halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
220 SkAutoTArray<float> halfKernel(halfKernelSize);
221 SkAutoTArray<float> summedKernel(halfKernelSize);
222 make_half_kernel_and_summed_table(halfKernel.get(), summedKernel.get(), half KernelSize,
223 sigma);
230 for (int i = 0; i < numSteps - 1; ++i) { 224 for (int i = 0; i < numSteps - 1; ++i) {
231 weights[i] = eval_at(offset+i, circleR, halfKernel.get(), kernelWH); 225 weights[i] = eval_at(offset+i, circleR, halfKernel.get(), summedKernel.g et(),
226 halfKernelSize);
232 } 227 }
233 // Ensure the tail of the Gaussian goes to zero. 228 // Ensure the tail of the Gaussian goes to zero.
234 weights[numSteps - 1] = 0; 229 weights[numSteps - 1] = 0;
235
236 return weights; 230 return weights;
237 } 231 }
238 232
239 GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( 233 GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture(
240 GrTextureProvide r* textureProvider, 234 GrTextureProvide r* textureProvider,
241 const SkRect& ci rcle, 235 const SkRect& ci rcle,
242 float sigma, 236 float sigma,
243 float* offset) { 237 float* offset) {
244 float circleR = circle.width() / 2.0f; 238 float circleR = circle.width() / 2.0f;
245 239
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
278 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); 272 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
279 273
280 const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessor TestData* d) { 274 const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessor TestData* d) {
281 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); 275 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f);
282 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); 276 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f);
283 SkRect circle = SkRect::MakeWH(wh, wh); 277 SkRect circle = SkRect::MakeWH(wh, wh);
284 return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma); 278 return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma);
285 } 279 }
286 280
287 #endif 281 #endif
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698