Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
BlurUtils.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2023 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7#include "src/gpu/BlurUtils.h"
8
12#include "include/core/SkM44.h"
14#include "include/core/SkRect.h"
16#include "include/core/SkSize.h"
22#include "src/base/SkMathPriv.h"
24
25#include <algorithm>
26#include <array>
27#include <cmath>
28#include <cstdint>
29#include <cstring>
30#include <memory>
31#include <vector>
32
33namespace skgpu {
34
36 SkISize radius,
37 SkSpan<float> kernel) {
38 // Callers likely had to calculate the radius prior to filling out the kernel value, which is
39 // why it's provided; but make sure it's consistent with expectations.
40 SkASSERT(BlurSigmaRadius(sigma.width()) == radius.width() &&
41 BlurSigmaRadius(sigma.height()) == radius.height());
42
43 // Callers are responsible for downscaling large sigmas to values that can be processed by the
44 // effects, so ensure the radius won't overflow 'kernel'
45 const int width = BlurKernelWidth(radius.width());
46 const int height = BlurKernelWidth(radius.height());
47 const size_t kernelSize = SkTo<size_t>(sk_64_mul(width, height));
48 SkASSERT(kernelSize <= kernel.size());
49
50 // And the definition of an identity blur should be sufficient that 2sigma^2 isn't near zero
51 // when there's a non-trivial radius.
52 const float twoSigmaSqrdX = 2.0f * sigma.width() * sigma.width();
53 const float twoSigmaSqrdY = 2.0f * sigma.height() * sigma.height();
54 SkASSERT((radius.width() == 0 || !SkScalarNearlyZero(twoSigmaSqrdX)) &&
55 (radius.height() == 0 || !SkScalarNearlyZero(twoSigmaSqrdY)));
56
57 // Setting the denominator to 1 when the radius is 0 automatically converts the remaining math
58 // to the 1D Gaussian distribution. When both radii are 0, it correctly computes a weight of 1.0
59 const float sigmaXDenom = radius.width() > 0 ? 1.0f / twoSigmaSqrdX : 1.f;
60 const float sigmaYDenom = radius.height() > 0 ? 1.0f / twoSigmaSqrdY : 1.f;
61
62 float sum = 0.0f;
63 for (int x = 0; x < width; x++) {
64 float xTerm = static_cast<float>(x - radius.width());
65 xTerm = xTerm * xTerm * sigmaXDenom;
66 for (int y = 0; y < height; y++) {
67 float yTerm = static_cast<float>(y - radius.height());
68 float xyTerm = std::exp(-(xTerm + yTerm * yTerm * sigmaYDenom));
69 // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
70 // is dropped here, since we renormalize the kernel below.
71 kernel[y * width + x] = xyTerm;
72 sum += xyTerm;
73 }
74 }
75 // Normalize the kernel
76 float scale = 1.0f / sum;
77 for (size_t i = 0; i < kernelSize; ++i) {
78 kernel[i] *= scale;
79 }
80 // Zero remainder of the array
81 memset(kernel.data() + kernelSize, 0, sizeof(float)*(kernel.size() - kernelSize));
82}
83
85 SkISize radii,
86 std::array<SkV4, kMaxBlurSamples/4>& kernel) {
87 static_assert(sizeof(kernel) == sizeof(std::array<float, kMaxBlurSamples>));
88 static_assert(alignof(float) == alignof(SkV4));
89 float* data = kernel[0].ptr();
91}
92
93void Compute2DBlurOffsets(SkISize radius, std::array<SkV4, kMaxBlurSamples/2>& offsets) {
94 const int kernelArea = BlurKernelWidth(radius.width()) * BlurKernelWidth(radius.height());
95 SkASSERT(kernelArea <= kMaxBlurSamples);
96
97 SkSpan<float> offsetView{offsets[0].ptr(), kMaxBlurSamples*2};
98
99 int i = 0;
100 for (int y = -radius.height(); y <= radius.height(); ++y) {
101 for (int x = -radius.width(); x <= radius.width(); ++x) {
102 offsetView[2*i] = x;
103 offsetView[2*i+1] = y;
104 ++i;
105 }
106 }
107 SkASSERT(i == kernelArea);
108 const int lastValidOffset = 2*(kernelArea - 1);
109 for (; i < kMaxBlurSamples; ++i) {
110 offsetView[2*i] = offsetView[lastValidOffset];
111 offsetView[2*i+1] = offsetView[lastValidOffset+1];
112 }
113}
114
116 int radius,
117 std::array<SkV4, kMaxBlurSamples/2>& offsetsAndKernel) {
119 SkASSERT(radius == BlurSigmaRadius(sigma));
121
122 // Given 2 adjacent gaussian points, they are blended as: Wi * Ci + Wj * Cj.
123 // The GPU will mix Ci and Cj as Ci * (1 - x) + Cj * x during sampling.
124 // Compute W', x such that W' * (Ci * (1 - x) + Cj * x) = Wi * Ci + Wj * Cj.
125 // Solving W' * x = Wj, W' * (1 - x) = Wi:
126 // W' = Wi + Wj
127 // x = Wj / (Wi + Wj)
128 auto get_new_weight = [](float* new_w, float* offset, float wi, float wj) {
129 *new_w = wi + wj;
130 *offset = wj / (wi + wj);
131 };
132
133 // Create a temporary standard kernel. The maximum blur radius that can be passed to this
134 // function is (kMaxBlurSamples-1), so make an array large enough to hold the full kernel width.
135 static constexpr int kMaxKernelWidth = BlurKernelWidth(kMaxBlurSamples - 1);
136 SkASSERT(BlurKernelWidth(radius) <= kMaxKernelWidth);
137 std::array<float, kMaxKernelWidth> fullKernel;
138 Compute1DBlurKernel(sigma, radius, SkSpan<float>{fullKernel.data(), BlurKernelWidth(radius)});
139
140 std::array<float, kMaxBlurSamples> kernel;
141 std::array<float, kMaxBlurSamples> offsets;
142 // Note that halfsize isn't just size / 2, but radius + 1. This is the size of the output array.
143 int halfSize = skgpu::BlurLinearKernelWidth(radius);
144 int halfRadius = halfSize / 2;
145 int lowIndex = halfRadius - 1;
146
147 // Compute1DGaussianKernel produces a full 2N + 1 kernel. Since the kernel can be mirrored,
148 // compute only the upper half and mirror to the lower half.
149
150 int index = radius;
151 if (radius & 1) {
152 // If N is odd, then use two samples.
153 // The centre texel gets sampled twice, so halve its influence for each sample.
154 // We essentially sample like this:
155 // Texel edges
156 // v v v v
157 // | | | |
158 // \-----^---/ Lower sample
159 // \---^-----/ Upper sample
160 get_new_weight(&kernel[halfRadius],
161 &offsets[halfRadius],
162 fullKernel[index] * 0.5f,
163 fullKernel[index + 1]);
164 kernel[lowIndex] = kernel[halfRadius];
165 offsets[lowIndex] = -offsets[halfRadius];
166 index++;
167 lowIndex--;
168 } else {
169 // If N is even, then there are an even number of texels on either side of the centre texel.
170 // Sample the centre texel directly.
171 kernel[halfRadius] = fullKernel[index];
172 offsets[halfRadius] = 0.0f;
173 }
174 index++;
175
176 // Every other pair gets one sample.
177 for (int i = halfRadius + 1; i < halfSize; index += 2, i++, lowIndex--) {
178 get_new_weight(&kernel[i], &offsets[i], fullKernel[index], fullKernel[index + 1]);
179 offsets[i] += static_cast<float>(index - radius);
180
181 // Mirror to lower half.
182 kernel[lowIndex] = kernel[i];
183 offsets[lowIndex] = -offsets[i];
184 }
185
186 // Zero out remaining values in the kernel
187 memset(kernel.data() + halfSize, 0, sizeof(float)*(kMaxBlurSamples - halfSize));
188 // But copy the last valid offset into the remaining offsets, to increase the chance that
189 // over-iteration in a fragment shader will have a cache hit.
190 for (int i = halfSize; i < kMaxBlurSamples; ++i) {
191 offsets[i] = offsets[halfSize - 1];
192 }
193
194 // Interleave into the output array to match the 1D SkSL effect
195 for (int i = 0; i < skgpu::kMaxBlurSamples / 2; ++i) {
196 offsetsAndKernel[i] = SkV4{offsets[2*i], kernel[2*i], offsets[2*i+1], kernel[2*i+1]};
197 }
198}
199
200static SkKnownRuntimeEffects::StableKey to_stablekey(int kernelWidth, uint32_t baseKey) {
201 SkASSERT(kernelWidth >= 2 && kernelWidth <= kMaxBlurSamples);
202 switch(kernelWidth) {
203 // Batch on multiples of 4 (skipping width=1, since that can't happen)
204 case 2: [[fallthrough]];
205 case 3: [[fallthrough]];
206 case 4: return static_cast<SkKnownRuntimeEffects::StableKey>(baseKey);
207 case 5: [[fallthrough]];
208 case 6: [[fallthrough]];
209 case 7: [[fallthrough]];
210 case 8: return static_cast<SkKnownRuntimeEffects::StableKey>(baseKey+1);
211 case 9: [[fallthrough]];
212 case 10: [[fallthrough]];
213 case 11: [[fallthrough]];
214 case 12: return static_cast<SkKnownRuntimeEffects::StableKey>(baseKey+2);
215 case 13: [[fallthrough]];
216 case 14: [[fallthrough]];
217 case 15: [[fallthrough]];
218 case 16: return static_cast<SkKnownRuntimeEffects::StableKey>(baseKey+3);
219 case 17: [[fallthrough]];
220 case 18: [[fallthrough]];
221 case 19: [[fallthrough]];
222 // With larger kernels, batch on multiples of eight so up to 7 wasted samples.
223 case 20: return static_cast<SkKnownRuntimeEffects::StableKey>(baseKey+4);
224 case 21: [[fallthrough]];
225 case 22: [[fallthrough]];
226 case 23: [[fallthrough]];
227 case 24: [[fallthrough]];
228 case 25: [[fallthrough]];
229 case 26: [[fallthrough]];
230 case 27: [[fallthrough]];
231 case 28: return static_cast<SkKnownRuntimeEffects::StableKey>(baseKey+5);
232 default:
234 }
235}
236
238 return GetKnownRuntimeEffect(
240 static_cast<uint32_t>(SkKnownRuntimeEffects::StableKey::k1DBlurBase)));
241}
242
244 int kernelArea = BlurKernelWidth(radii.width()) * BlurKernelWidth(radii.height());
245 return GetKnownRuntimeEffect(
246 to_stablekey(kernelArea,
247 static_cast<uint32_t>(SkKnownRuntimeEffects::StableKey::k2DBlurBase)));
248}
249
250///////////////////////////////////////////////////////////////////////////////
251// Rect Blur
252///////////////////////////////////////////////////////////////////////////////
253
254// TODO: it seems like there should be some synergy with SkBlurMask::ComputeBlurProfile
255// TODO: maybe cache this on the cpu side?
258
259 int width = ComputeIntegralTableWidth(sixSigma);
260 if (width == 0) {
261 return table;
262 }
263
264 if (!table.tryAllocPixels(SkImageInfo::MakeA8(width, 1))) {
265 return table;
266 }
267 *table.getAddr8(0, 0) = 255;
268 const float invWidth = 1.f / width;
269 for (int i = 1; i < width - 1; ++i) {
270 float x = (i + 0.5f) * invWidth;
271 x = (-6 * x + 3) * SK_ScalarRoot2Over2;
272 float integral = 0.5f * (std::erf(x) + 1.f);
273 *table.getAddr8(i, 0) = SkToU8(sk_float_round2int(255.f * integral));
274 }
275
276 *table.getAddr8(width - 1, 0) = 0;
277 table.setImmutable();
278 return table;
279}
280
281int ComputeIntegralTableWidth(float sixSigma) {
282 // Check for NaN/infinity
283 if (!SkIsFinite(sixSigma)) {
284 return 0;
285 }
286 // Avoid overflow, covers both multiplying by 2 and finding next power of 2:
287 // 2*((2^31-1)/4 + 1) = 2*(2^29-1) + 2 = 2^30 and SkNextPow2(2^30) = 2^30
288 if (sixSigma > SK_MaxS32 / 4 + 1) {
289 return 0;
290 }
291 // The texture we're producing represents the integral of a normal distribution over a
292 // six-sigma range centered at zero. We want enough resolution so that the linear
293 // interpolation done in texture lookup doesn't introduce noticeable artifacts. We
294 // conservatively choose to have 2 texels for each dst pixel.
295 int minWidth = 2 * ((int)std::ceil(sixSigma));
296 // Bin by powers of 2 with a minimum so we get good profile reuse.
297 return std::max(SkNextPow2(minWidth), 32);
298}
299
300///////////////////////////////////////////////////////////////////////////////
301// Circle Blur
302///////////////////////////////////////////////////////////////////////////////
303
304// Computes an unnormalized half kernel (right side). Returns the summation of all the half
305// kernel values.
306static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) {
307 const float invSigma = 1.0f / sigma;
308 const float b = -0.5f * invSigma * invSigma;
309 float tot = 0.0f;
310 // Compute half kernel values at half pixel steps out from the center.
311 float t = 0.5f;
312 for (int i = 0; i < halfKernelSize; ++i) {
313 float value = expf(t * t * b);
314 tot += value;
315 halfKernel[i] = value;
316 t += 1.0f;
317 }
318 return tot;
319}
320
321// Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number
322// of discrete steps. The half kernel is normalized to sum to 0.5.
323static void make_half_kernel_and_summed_table(float* halfKernel,
324 float* summedHalfKernel,
325 int halfKernelSize,
326 float sigma) {
327 // The half kernel should sum to 0.5 not 1.0.
328 const float tot = 2.0f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma);
329 float sum = 0.0f;
330 for (int i = 0; i < halfKernelSize; ++i) {
331 halfKernel[i] /= tot;
332 sum += halfKernel[i];
333 summedHalfKernel[i] = sum;
334 }
335}
336
337// Applies the 1D half kernel vertically at points along the x axis to a circle centered at the
338// origin with radius circleR.
339static void apply_kernel_in_y(float* results,
340 int numSteps,
341 float firstX,
342 float circleR,
343 int halfKernelSize,
344 const float* summedHalfKernelTable) {
345 float x = firstX;
346 for (int i = 0; i < numSteps; ++i, x += 1.0f) {
347 if (x < -circleR || x > circleR) {
348 results[i] = 0;
349 continue;
350 }
351 float y = sqrtf(circleR * circleR - x * x);
352 // In the column at x we exit the circle at +y and -y
353 // The summed table entry j is actually reflects an offset of j + 0.5.
354 y -= 0.5f;
355 int yInt = SkScalarFloorToInt(y);
356 SkASSERT(yInt >= -1);
357 if (y < 0) {
358 results[i] = (y + 0.5f) * summedHalfKernelTable[0];
359 } else if (yInt >= halfKernelSize - 1) {
360 results[i] = 0.5f;
361 } else {
362 float yFrac = y - yInt;
363 results[i] = (1.0f - yFrac) * summedHalfKernelTable[yInt] +
364 yFrac * summedHalfKernelTable[yInt + 1];
365 }
366 }
367}
368
369// Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR.
370// This relies on having a half kernel computed for the Gaussian and a table of applications of
371// the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX +
372// halfKernel) passed in as yKernelEvaluations.
373static uint8_t eval_at(float evalX,
374 float circleR,
375 const float* halfKernel,
376 int halfKernelSize,
377 const float* yKernelEvaluations) {
378 float acc = 0;
379
380 float x = evalX - halfKernelSize;
381 for (int i = 0; i < halfKernelSize; ++i, x += 1.0f) {
382 if (x < -circleR || x > circleR) {
383 continue;
384 }
385 float verticalEval = yKernelEvaluations[i];
386 acc += verticalEval * halfKernel[halfKernelSize - i - 1];
387 }
388 for (int i = 0; i < halfKernelSize; ++i, x += 1.0f) {
389 if (x < -circleR || x > circleR) {
390 continue;
391 }
392 float verticalEval = yKernelEvaluations[i + halfKernelSize];
393 acc += verticalEval * halfKernel[i];
394 }
395 // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about
396 // the x axis).
397 return SkUnitScalarClampToByte(2.0f * acc);
398}
399
400// This function creates a profile of a blurred circle. It does this by computing a kernel for
401// half the Gaussian and a matching summed area table. The summed area table is used to compute
402// an array of vertical applications of the half kernel to the circle along the x axis. The
403// table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is
404// the size of the profile being computed. Then for each of the n profile entries we walk out k
405// steps in each horizontal direction multiplying the corresponding y evaluation by the half
406// kernel entry and sum these values to compute the profile entry.
407SkBitmap CreateCircleProfile(float sigma, float radius, int profileWidth) {
409 if (!bitmap.tryAllocPixels(SkImageInfo::MakeA8(profileWidth, 1))) {
410 return bitmap;
411 }
412
413 uint8_t* profile = bitmap.getAddr8(0, 0);
414
415 const int numSteps = profileWidth;
416
417 // The full kernel is 6 sigmas wide.
418 int halfKernelSize = SkScalarCeilToInt(6.0f * sigma);
419 // Round up to next multiple of 2 and then divide by 2.
420 halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
421
422 // Number of x steps at which to apply kernel in y to cover all the profile samples in x.
423 const int numYSteps = numSteps + 2 * halfKernelSize;
424
425 skia_private::AutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps);
426 float* halfKernel = bulkAlloc.get();
427 float* summedKernel = bulkAlloc.get() + halfKernelSize;
428 float* yEvals = bulkAlloc.get() + 2 * halfKernelSize;
429 make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma);
430
431 float firstX = -halfKernelSize + 0.5f;
432 apply_kernel_in_y(yEvals, numYSteps, firstX, radius, halfKernelSize, summedKernel);
433
434 for (int i = 0; i < numSteps - 1; ++i) {
435 float evalX = i + 0.5f;
436 profile[i] = eval_at(evalX, radius, halfKernel, halfKernelSize, yEvals + i);
437 }
438 // Ensure the tail of the Gaussian goes to zero.
439 profile[numSteps - 1] = 0;
440
441 bitmap.setImmutable();
442 return bitmap;
443}
444
446 SkASSERT(!(profileWidth & 0x1));
447
449 if (!bitmap.tryAllocPixels(SkImageInfo::MakeA8(profileWidth, 1))) {
450 return bitmap;
451 }
452
453 uint8_t* profile = bitmap.getAddr8(0, 0);
454
455 // The full kernel is 6 sigmas wide.
456 const float sigma = profileWidth / 6.0f;
457 const int halfKernelSize = profileWidth / 2;
458
459 skia_private::AutoTArray<float> halfKernel(halfKernelSize);
460
461 // The half kernel should sum to 0.5.
462 const float tot = 2.0f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, sigma);
463 float sum = 0.0f;
464 // Populate the profile from the right edge to the middle.
465 for (int i = 0; i < halfKernelSize; ++i) {
466 halfKernel[halfKernelSize - i - 1] /= tot;
467 sum += halfKernel[halfKernelSize - i - 1];
468 profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum);
469 }
470 // Populate the profile from the middle to the left edge (by flipping the half kernel and
471 // continuing the summation).
472 for (int i = 0; i < halfKernelSize; ++i) {
473 sum += halfKernel[i];
474 profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum);
475 }
476 // Ensure the tail of the Gaussian goes to zero.
477 profile[profileWidth - 1] = 0;
478
479 bitmap.setImmutable();
480 return bitmap;
481}
482
483///////////////////////////////////////////////////////////////////////////////
484// RRect Blur
485///////////////////////////////////////////////////////////////////////////////
486
487// Evaluate the vertical blur at the specified 'y' value given the location of the top of the
488// rrect.
489static uint8_t eval_V(float top, int y, const uint8_t* integral, int integralSize, float sixSigma) {
490 if (top < 0) {
491 return 0; // an empty column
492 }
493
494 float fT = (top - y - 0.5f) * (integralSize / sixSigma);
495 if (fT < 0) {
496 return 255;
497 } else if (fT >= integralSize - 1) {
498 return 0;
499 }
500
501 int lower = (int)fT;
502 float frac = fT - lower;
503
504 SkASSERT(lower + 1 < integralSize);
505
506 return integral[lower] * (1.0f - frac) + integral[lower + 1] * frac;
507}
508
509// Apply a gaussian 'kernel' horizontally at the specified 'x', 'y' location.
510static uint8_t eval_H(int x,
511 int y,
512 const std::vector<float>& topVec,
513 const float* kernel,
514 int kernelSize,
515 const uint8_t* integral,
516 int integralSize,
517 float sixSigma) {
518 SkASSERT(0 <= x && x < (int)topVec.size());
519 SkASSERT(kernelSize % 2);
520
521 float accum = 0.0f;
522
523 int xSampleLoc = x - (kernelSize / 2);
524 for (int i = 0; i < kernelSize; ++i, ++xSampleLoc) {
525 if (xSampleLoc < 0 || xSampleLoc >= (int)topVec.size()) {
526 continue;
527 }
528
529 accum += kernel[i] * eval_V(topVec[xSampleLoc], y, integral, integralSize, sixSigma);
530 }
531
532 return accum + 0.5f;
533}
534
535SkBitmap CreateRRectBlurMask(const SkRRect& rrectToDraw, const SkISize& dimensions, float sigma) {
537 int radius = skgpu::BlurSigmaRadius(sigma);
538 int kernelSize = skgpu::BlurKernelWidth(radius);
539
540 SkASSERT(kernelSize % 2);
541 SkASSERT(dimensions.width() % 2);
542 SkASSERT(dimensions.height() % 2);
543
544 SkVector radii = rrectToDraw.getSimpleRadii();
545 SkASSERT(SkScalarNearlyEqual(radii.fX, radii.fY));
546
547 const int halfWidthPlus1 = (dimensions.width() / 2) + 1;
548 const int halfHeightPlus1 = (dimensions.height() / 2) + 1;
549
550 std::unique_ptr<float[]> kernel(new float[kernelSize]);
551 skgpu::Compute1DBlurKernel(sigma, radius, SkSpan<float>(kernel.get(), kernelSize));
552
553 SkBitmap integral = CreateIntegralTable(6.0f * sigma);
554 if (integral.empty()) {
555 return {};
556 }
557
559 if (!result.tryAllocPixels(SkImageInfo::MakeA8(dimensions.width(), dimensions.height()))) {
560 return {};
561 }
562
563 std::vector<float> topVec;
564 topVec.reserve(dimensions.width());
565 for (int x = 0; x < dimensions.width(); ++x) {
566 if (x < rrectToDraw.rect().fLeft || x > rrectToDraw.rect().fRight) {
567 topVec.push_back(-1);
568 } else {
569 if (x + 0.5f < rrectToDraw.rect().fLeft + radii.fX) { // in the circular section
570 float xDist = rrectToDraw.rect().fLeft + radii.fX - x - 0.5f;
571 float h = sqrtf(radii.fX * radii.fX - xDist * xDist);
572 SkASSERT(0 <= h && h < radii.fY);
573 topVec.push_back(rrectToDraw.rect().fTop + radii.fX - h + 3 * sigma);
574 } else {
575 topVec.push_back(rrectToDraw.rect().fTop + 3 * sigma);
576 }
577 }
578 }
579
580 for (int y = 0; y < halfHeightPlus1; ++y) {
581 uint8_t* scanline = result.getAddr8(0, y);
582
583 for (int x = 0; x < halfWidthPlus1; ++x) {
584 scanline[x] = eval_H(x,
585 y,
586 topVec,
587 kernel.get(),
588 kernelSize,
589 integral.getAddr8(0, 0),
590 integral.width(),
591 6.0f * sigma);
592 scanline[dimensions.width() - x - 1] = scanline[x];
593 }
594
595 memcpy(result.getAddr8(0, dimensions.height() - y - 1), scanline, result.rowBytes());
596 }
597
598 result.setImmutable();
599 return result;
600}
601
602} // namespace skgpu
#define SkUNREACHABLE
Definition SkAssert.h:135
#define SkASSERT(cond)
Definition SkAssert.h:116
static U8CPU SkUnitScalarClampToByte(SkScalar x)
Definition SkColorPriv.h:36
static bool SkIsFinite(T x, Pack... values)
#define sk_float_round2int(x)
static int SkNextPow2(int value)
Definition SkMathPriv.h:272
static int64_t sk_64_mul(int64_t a, int64_t b)
Definition SkMath.h:33
static constexpr int32_t SK_MaxS32
Definition SkMath.h:21
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
#define SkScalarCeilToInt(x)
Definition SkScalar.h:36
#define SkScalarFloorToInt(x)
Definition SkScalar.h:35
#define SK_ScalarRoot2Over2
Definition SkScalar.h:23
constexpr uint8_t SkToU8(S x)
Definition SkTo.h:22
SI F table(const skcms_Curve *curve, F v)
Type::kYUV Type::kRGBA() int(0.7 *637)
bool empty() const
Definition SkBitmap.h:210
uint8_t * getAddr8(int x, int y) const
Definition SkBitmap.h:1270
int width() const
Definition SkBitmap.h:149
SkVector getSimpleRadii() const
Definition SkRRect.h:111
const SkRect & rect() const
Definition SkRRect.h:264
constexpr T * data() const
Definition SkSpan_impl.h:94
constexpr size_t size() const
Definition SkSpan_impl.h:95
static bool b
uint8_t value
GAsyncResult * result
double y
double x
constexpr bool BlurIsEffectivelyIdentity(float sigma)
Definition BlurUtils.h:37
static uint8_t eval_H(int x, int y, const std::vector< float > &topVec, const float *kernel, int kernelSize, const uint8_t *integral, int integralSize, float sixSigma)
SkBitmap CreateRRectBlurMask(const SkRRect &rrectToDraw, const SkISize &dimensions, float sigma)
static constexpr int kMaxBlurSamples
Definition BlurUtils.h:52
SkBitmap CreateIntegralTable(float sixSigma)
int ComputeIntegralTableWidth(float sixSigma)
static constexpr float kMaxLinearBlurSigma
Definition BlurUtils.h:58
void Compute1DBlurKernel(float sigma, int radius, SkSpan< float > kernel)
Definition BlurUtils.h:118
void Compute2DBlurOffsets(SkISize radius, std::array< SkV4, kMaxBlurSamples/2 > &offsets)
Definition BlurUtils.cpp:93
static uint8_t eval_at(float evalX, float circleR, const float *halfKernel, int halfKernelSize, const float *yKernelEvaluations)
SkBitmap CreateCircleProfile(float sigma, float radius, int profileWidth)
static SkKnownRuntimeEffects::StableKey to_stablekey(int kernelWidth, uint32_t baseKey)
static void apply_kernel_in_y(float *results, int numSteps, float firstX, float circleR, int halfKernelSize, const float *summedHalfKernelTable)
const SkRuntimeEffect * GetLinearBlur1DEffect(int radius)
int BlurSigmaRadius(float sigma)
Definition BlurUtils.h:41
static uint8_t eval_V(float top, int y, const uint8_t *integral, int integralSize, float sixSigma)
constexpr int BlurKernelWidth(int radius)
Definition BlurUtils.h:28
static void make_half_kernel_and_summed_table(float *halfKernel, float *summedHalfKernel, int halfKernelSize, float sigma)
void Compute2DBlurKernel(SkSize sigma, SkISize radius, SkSpan< float > kernel)
Definition BlurUtils.cpp:35
constexpr int BlurLinearKernelWidth(int radius)
Definition BlurUtils.h:32
SkBitmap CreateHalfPlaneProfile(int profileWidth)
static float make_unnormalized_half_kernel(float *halfKernel, int halfKernelSize, float sigma)
const SkRuntimeEffect * GetBlur2DEffect(const SkISize &radii)
void Compute1DBlurLinearKernel(float sigma, int radius, std::array< SkV4, kMaxBlurSamples/2 > &offsetsAndKernel)
SkScalar h
int32_t height
int32_t width
const Scalar scale
Point offset
constexpr int32_t width() const
Definition SkSize.h:36
constexpr int32_t height() const
Definition SkSize.h:37
static SkImageInfo MakeA8(int width, int height)
float fX
x-axis value
float fY
y-axis value
SkScalar fLeft
smaller x-axis bounds
Definition extension.cpp:14
SkScalar fRight
larger x-axis bounds
Definition extension.cpp:16
SkScalar fTop
smaller y-axis bounds
Definition extension.cpp:15
SkScalar width() const
Definition SkSize.h:76
SkScalar height() const
Definition SkSize.h:77
Definition SkM44.h:98