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 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
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 // Evaluate an AA circle function centered at the origin with 'radius' at (x,y) |
116 static inline float disk(float x, float y, float radius) { | 116 static inline float disk(float x, float y, float radius) { |
117 float distSq = x*x + y*y; | 117 float distSq = x*x + y*y; |
118 if (distSq <= (radius-0.5f)*(radius-0.5f)) { | 118 if (distSq <= (radius - 0.5f) * (radius - 0.5f)) { |
119 return 1.0f; | 119 return 1.0f; |
120 } else if (distSq >= (radius+0.5f)*(radius+0.5f)) { | 120 } else if (distSq >= (radius + 0.5f) * (radius + 0.5f)) { |
121 return 0.0f; | 121 return 0.0f; |
122 } else { | 122 } else { |
123 float ramp = radius + 0.5f - sqrtf(distSq); | 123 float ramp = radius + 0.5f - sqrtf(distSq); |
124 SkASSERT(ramp >= 0.0f && ramp <= 1.0f); | 124 SkASSERT(ramp >= 0.0f && ramp <= 1.0f); |
125 return ramp; | 125 return ramp; |
126 } | 126 } |
127 } | 127 } |
128 | 128 |
129 // Create the top half of an even-sized Gaussian kernel | 129 // Create the top half of an even-sized Gaussian kernel |
130 static void make_half_kernel(float* kernel, int kernelWH, float sigma) { | 130 static void make_half_kernel(float* kernel, int kernelWH, float sigma) { |
131 SkASSERT(!(kernelWH & 1)); | 131 SkASSERT(!(kernelWH & 1)); |
132 | 132 |
133 const float kernelOff = (kernelWH-1)/2.0f; | 133 // We treat each cell in the half-kernel as a 1x1 window and evaluate it |
| 134 // at the center. So the evaluations go from -kernelOff to kernelOff in x |
| 135 // and -kernelOff to -.5 in y (since this is a top-half kernel). |
| 136 const float kernelOff = (kernelWH - 1) / 2.0f; |
134 | 137 |
135 float b = 1.0f / (2.0f * sigma * sigma); | 138 float b = 1.0f / (2.0f * sigma * sigma); |
136 // omit the scale term since we're just going to renormalize | 139 // omit the scale term since we're just going to renormalize |
137 | 140 |
138 float tot = 0.0f; | 141 float tot = 0.0f; |
139 for (int y = 0; y < kernelWH/2; ++y) { | 142 for (int y = 0; y < kernelWH / 2; ++y) { |
140 for (int x = 0; x < kernelWH/2; ++x) { | 143 for (int x = 0; x < kernelWH / 2; ++x) { |
141 // TODO: use a cheap approximation of the 2D Guassian? | 144 // TODO: use a cheap approximation of the 2D Guassian? |
142 float x2 = (x-kernelOff) * (x-kernelOff); | 145 float x2 = (x - kernelOff) * (x - kernelOff); |
143 float y2 = (y-kernelOff) * (y-kernelOff); | 146 float y2 = (y - kernelOff) * (y - kernelOff); |
144 // The kernel is symmetric so only compute it once for both sides | 147 // The kernel is symmetric so only compute it once for both sides |
145 kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = expf(-(x2
+ y2) * b); | 148 float value = expf(-(x2 + y2) * b); |
146 tot += 2.0f * kernel[y*kernelWH+x]; | 149 kernel[y * kernelWH + x] = value; |
| 150 kernel[y * kernelWH + (kernelWH - x - 1)] = value; |
| 151 tot += 2.0f * value; |
147 } | 152 } |
148 } | 153 } |
149 // Still normalize the half kernel to 1.0 (rather than 0.5) so we don't | 154 // Normalize the half kernel to 1.0 (rather than 0.5) so we don't have to sc
ale by 2.0 after |
150 // have to scale by 2.0 after convolution. | 155 // convolution. |
151 for (int y = 0; y < kernelWH/2; ++y) { | 156 for (int y = 0; y < kernelWH / 2; ++y) { |
152 for (int x = 0; x < kernelWH; ++x) { | 157 for (int x = 0; x < kernelWH; ++x) { |
153 kernel[y*kernelWH+x] /= tot; | 158 kernel[y * kernelWH + x] /= tot; |
154 } | 159 } |
155 } | 160 } |
156 } | 161 } |
157 | 162 |
158 // Apply the half-kernel at 't' away from the center of the circle | 163 // Apply the half-kernel at 't' away from the center of the circle |
159 static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH
) { | 164 static uint8_t eval_at(float t, float circleR, float* halfKernel, int kernelWH)
{ |
160 SkASSERT(!(kernelWH & 1)); | 165 SkASSERT(!(kernelWH & 1)); |
161 | 166 |
162 const float kernelOff = (kernelWH-1)/2.0f; | |
163 | |
164 float acc = 0; | 167 float acc = 0; |
165 | 168 |
166 for (int y = 0; y < kernelWH/2; ++y) { | 169 // We evaluate the kernel application at (x=t, y=0) using halfKernel which r
epresents the top |
167 if (kernelOff-y > halfWidth+0.5f) { | 170 // half of a 2D Guassian kernel. The full kernel is symmetric so evaluating
just the upper half |
168 // All disk() samples in this row will be 0.0f | 171 // is sufficient. The half kernel has been normalized to 1 rather than 0.5 s
o there is no need |
| 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. |
169 continue; | 181 continue; |
170 } | 182 } |
171 | 183 |
172 for (int x = 0; x < kernelWH; ++x) { | 184 for (int i = 0; i < kernelWH; ++i) { |
173 float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth); | 185 float x = t - kernelOff + i; |
174 float kernel = halfKernel[y*kernelWH+x]; | 186 if (x > circleR + 0.5f) { |
| 187 // Stop evaluation once x crosses outside the circle. |
| 188 break; |
| 189 } |
| 190 float image = disk(x, y, circleR); |
| 191 float kernel = halfKernel[j * kernelWH + i]; |
175 acc += kernel * image; | 192 acc += kernel * image; |
176 } | 193 } |
177 } | 194 } |
178 | 195 |
179 return SkUnitScalarClampToByte(acc); | 196 return SkUnitScalarClampToByte(acc); |
180 } | 197 } |
181 | 198 |
182 static inline void compute_profile_offset_and_size(float halfWH, float sigma, | 199 static inline void compute_profile_offset_and_size(float circleR, float sigma, |
183 float* offset, int* size) { | 200 float* offset, int* size) { |
184 | 201 |
185 if (3*sigma <= halfWH) { | 202 if (3*sigma <= circleR) { |
186 // The circle is bigger than the Gaussian. In this case we know the inte
rior of the | 203 // The circle is bigger than the Gaussian. In this case we know the inte
rior of the |
187 // blurred circle is solid. | 204 // blurred circle is solid. |
188 *offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weigh
ts texture. | 205 *offset = circleR - 3 * sigma; // This location maps to 0.5f in the weig
hts texture. |
189 // It should always be 255. | 206 // It should always be 255. |
190 *size = SkScalarCeilToInt(6*sigma); | 207 *size = SkScalarCeilToInt(6*sigma); |
191 } else { | 208 } else { |
192 // The Gaussian is bigger than the circle. | 209 // The Gaussian is bigger than the circle. |
193 *offset = 0.0f; | 210 *offset = 0.0f; |
194 *size = SkScalarCeilToInt(halfWH + 3*sigma); | 211 *size = SkScalarCeilToInt(circleR + 3*sigma); |
195 } | 212 } |
196 } | 213 } |
197 | 214 |
198 static uint8_t* create_profile(float halfWH, float sigma) { | 215 static uint8_t* create_profile(float circleR, float sigma) { |
199 | 216 |
200 int kernelWH = SkScalarCeilToInt(6.0f*sigma); | 217 int kernelWH = SkScalarCeilToInt(6.0f*sigma); |
201 kernelWH = (kernelWH + 1) & ~1; // make it the next even number up | 218 kernelWH = (kernelWH + 1) & ~1; // make it the next even number up |
202 | 219 |
203 SkAutoTArray<float> halfKernel(kernelWH*kernelWH/2); | 220 SkAutoTArray<float> halfKernel(kernelWH * kernelWH / 2); |
204 | 221 |
205 make_half_kernel(halfKernel.get(), kernelWH, sigma); | 222 make_half_kernel(halfKernel.get(), kernelWH, sigma); |
206 | 223 |
207 float offset; | 224 float offset; |
208 int numSteps; | 225 int numSteps; |
209 | 226 |
210 compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps); | 227 compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps); |
211 | 228 |
212 uint8_t* weights = new uint8_t[numSteps]; | 229 uint8_t* weights = new uint8_t[numSteps]; |
213 for (int i = 0; i < numSteps - 1; ++i) { | 230 for (int i = 0; i < numSteps - 1; ++i) { |
214 weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH); | 231 weights[i] = eval_at(offset+i, circleR, halfKernel.get(), kernelWH); |
215 } | 232 } |
216 // Ensure the tail of the Gaussian goes to zero. | 233 // Ensure the tail of the Gaussian goes to zero. |
217 weights[numSteps-1] = 0; | 234 weights[numSteps - 1] = 0; |
218 | 235 |
219 return weights; | 236 return weights; |
220 } | 237 } |
221 | 238 |
222 GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( | 239 GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( |
223 GrTextureProvide
r* textureProvider, | 240 GrTextureProvide
r* textureProvider, |
224 const SkRect& ci
rcle, | 241 const SkRect& ci
rcle, |
225 float sigma, | 242 float sigma, |
226 float* offset) { | 243 float* offset) { |
227 float halfWH = circle.width() / 2.0f; | 244 float circleR = circle.width() / 2.0f; |
228 | |
229 int size; | |
230 compute_profile_offset_and_size(halfWH, sigma, offset, &size); | |
231 | |
232 GrSurfaceDesc texDesc; | |
233 texDesc.fWidth = size; | |
234 texDesc.fHeight = 1; | |
235 texDesc.fConfig = kAlpha_8_GrPixelConfig; | |
236 | 245 |
237 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); | 246 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); |
238 GrUniqueKey key; | 247 GrUniqueKey key; |
239 GrUniqueKey::Builder builder(&key, kDomain, 2); | 248 GrUniqueKey::Builder builder(&key, kDomain, 2); |
240 // The profile curve varies with both the sigma of the Gaussian and the size
of the | 249 // The profile curve varies with both the sigma of the Gaussian and the size
of the |
241 // disk. Quantizing to 16.16 should be close enough though. | 250 // disk. Quantizing to 16.16 should be close enough though. |
242 builder[0] = SkScalarToFixed(sigma); | 251 builder[0] = SkScalarToFixed(sigma); |
243 builder[1] = SkScalarToFixed(halfWH); | 252 builder[1] = SkScalarToFixed(circleR); |
244 builder.finish(); | 253 builder.finish(); |
245 | 254 |
246 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); | 255 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); |
247 | 256 |
| 257 int profileSize; |
| 258 compute_profile_offset_and_size(circleR, sigma, offset, &profileSize); |
| 259 |
248 if (!blurProfile) { | 260 if (!blurProfile) { |
249 SkAutoTDeleteArray<uint8_t> profile(create_profile(halfWH, sigma)); | 261 |
| 262 GrSurfaceDesc texDesc; |
| 263 texDesc.fWidth = profileSize; |
| 264 texDesc.fHeight = 1; |
| 265 texDesc.fConfig = kAlpha_8_GrPixelConfig; |
| 266 |
| 267 SkAutoTDeleteArray<uint8_t> profile(create_profile(circleR, sigma)); |
250 | 268 |
251 blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes,
profile.get(), 0); | 269 blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes,
profile.get(), 0); |
252 if (blurProfile) { | 270 if (blurProfile) { |
253 textureProvider->assignUniqueKeyToTexture(key, blurProfile); | 271 textureProvider->assignUniqueKeyToTexture(key, blurProfile); |
254 } | 272 } |
255 } | 273 } |
256 | 274 |
257 return blurProfile; | 275 return blurProfile; |
258 } | 276 } |
259 | 277 |
260 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); | 278 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); |
261 | 279 |
262 const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessor
TestData* d) { | 280 const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessor
TestData* d) { |
263 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); | 281 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); |
264 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); | 282 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); |
265 SkRect circle = SkRect::MakeWH(wh, wh); | 283 SkRect circle = SkRect::MakeWH(wh, wh); |
266 return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(),
circle, sigma); | 284 return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(),
circle, sigma); |
267 } | 285 } |
268 | 286 |
269 #endif | 287 #endif |
OLD | NEW |