OLD | NEW |
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 Loading... |
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 Loading... |
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 |
OLD | NEW |