1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2023 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/BlurUtils.h"
8*c8dee2aaSAndroid Build Coastguard Worker
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorPriv.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImageInfo.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRRect.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFloatingPoint.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkMath.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkPoint_impl.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTemplates.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTo.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkMathPriv.h"
23*c8dee2aaSAndroid Build Coastguard Worker
24*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
25*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
26*c8dee2aaSAndroid Build Coastguard Worker #include <cstdint>
27*c8dee2aaSAndroid Build Coastguard Worker #include <cstring>
28*c8dee2aaSAndroid Build Coastguard Worker #include <memory>
29*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
30*c8dee2aaSAndroid Build Coastguard Worker
31*c8dee2aaSAndroid Build Coastguard Worker namespace skgpu {
32*c8dee2aaSAndroid Build Coastguard Worker
33*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
34*c8dee2aaSAndroid Build Coastguard Worker // Rect Blur
35*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
36*c8dee2aaSAndroid Build Coastguard Worker
37*c8dee2aaSAndroid Build Coastguard Worker // TODO: it seems like there should be some synergy with SkBlurMask::ComputeBlurProfile
CreateIntegralTable(int width)38*c8dee2aaSAndroid Build Coastguard Worker SkBitmap CreateIntegralTable(int width) {
39*c8dee2aaSAndroid Build Coastguard Worker SkBitmap table;
40*c8dee2aaSAndroid Build Coastguard Worker
41*c8dee2aaSAndroid Build Coastguard Worker if (width <= 0) {
42*c8dee2aaSAndroid Build Coastguard Worker return table;
43*c8dee2aaSAndroid Build Coastguard Worker }
44*c8dee2aaSAndroid Build Coastguard Worker
45*c8dee2aaSAndroid Build Coastguard Worker if (!table.tryAllocPixels(SkImageInfo::MakeA8(width, 1))) {
46*c8dee2aaSAndroid Build Coastguard Worker return table;
47*c8dee2aaSAndroid Build Coastguard Worker }
48*c8dee2aaSAndroid Build Coastguard Worker *table.getAddr8(0, 0) = 255;
49*c8dee2aaSAndroid Build Coastguard Worker const float invWidth = 1.f / width;
50*c8dee2aaSAndroid Build Coastguard Worker for (int i = 1; i < width - 1; ++i) {
51*c8dee2aaSAndroid Build Coastguard Worker float x = (i + 0.5f) * invWidth;
52*c8dee2aaSAndroid Build Coastguard Worker x = (-6 * x + 3) * SK_ScalarRoot2Over2;
53*c8dee2aaSAndroid Build Coastguard Worker float integral = 0.5f * (std::erf(x) + 1.f);
54*c8dee2aaSAndroid Build Coastguard Worker *table.getAddr8(i, 0) = SkToU8(sk_float_round2int(255.f * integral));
55*c8dee2aaSAndroid Build Coastguard Worker }
56*c8dee2aaSAndroid Build Coastguard Worker
57*c8dee2aaSAndroid Build Coastguard Worker *table.getAddr8(width - 1, 0) = 0;
58*c8dee2aaSAndroid Build Coastguard Worker table.setImmutable();
59*c8dee2aaSAndroid Build Coastguard Worker return table;
60*c8dee2aaSAndroid Build Coastguard Worker }
61*c8dee2aaSAndroid Build Coastguard Worker
ComputeIntegralTableWidth(float sixSigma)62*c8dee2aaSAndroid Build Coastguard Worker int ComputeIntegralTableWidth(float sixSigma) {
63*c8dee2aaSAndroid Build Coastguard Worker // Check for NaN/infinity
64*c8dee2aaSAndroid Build Coastguard Worker if (!SkIsFinite(sixSigma)) {
65*c8dee2aaSAndroid Build Coastguard Worker return 0;
66*c8dee2aaSAndroid Build Coastguard Worker }
67*c8dee2aaSAndroid Build Coastguard Worker // Avoid overflow, covers both multiplying by 2 and finding next power of 2:
68*c8dee2aaSAndroid Build Coastguard Worker // 2*((2^31-1)/4 + 1) = 2*(2^29-1) + 2 = 2^30 and SkNextPow2(2^30) = 2^30
69*c8dee2aaSAndroid Build Coastguard Worker if (sixSigma > SK_MaxS32 / 4 + 1) {
70*c8dee2aaSAndroid Build Coastguard Worker return 0;
71*c8dee2aaSAndroid Build Coastguard Worker }
72*c8dee2aaSAndroid Build Coastguard Worker // The texture we're producing represents the integral of a normal distribution over a
73*c8dee2aaSAndroid Build Coastguard Worker // six-sigma range centered at zero. We want enough resolution so that the linear
74*c8dee2aaSAndroid Build Coastguard Worker // interpolation done in texture lookup doesn't introduce noticeable artifacts. We
75*c8dee2aaSAndroid Build Coastguard Worker // conservatively choose to have 2 texels for each dst pixel.
76*c8dee2aaSAndroid Build Coastguard Worker int minWidth = 2 * ((int)std::ceil(sixSigma));
77*c8dee2aaSAndroid Build Coastguard Worker // Bin by powers of 2 with a minimum so we get good profile reuse.
78*c8dee2aaSAndroid Build Coastguard Worker return std::max(SkNextPow2(minWidth), 32);
79*c8dee2aaSAndroid Build Coastguard Worker }
80*c8dee2aaSAndroid Build Coastguard Worker
81*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
82*c8dee2aaSAndroid Build Coastguard Worker // Circle Blur
83*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
84*c8dee2aaSAndroid Build Coastguard Worker
85*c8dee2aaSAndroid Build Coastguard Worker // Computes an unnormalized half kernel (right side). Returns the summation of all the half
86*c8dee2aaSAndroid Build Coastguard Worker // kernel values.
make_unnormalized_half_kernel(float * halfKernel,int halfKernelSize,float sigma)87*c8dee2aaSAndroid Build Coastguard Worker static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) {
88*c8dee2aaSAndroid Build Coastguard Worker const float invSigma = 1.0f / sigma;
89*c8dee2aaSAndroid Build Coastguard Worker const float b = -0.5f * invSigma * invSigma;
90*c8dee2aaSAndroid Build Coastguard Worker float tot = 0.0f;
91*c8dee2aaSAndroid Build Coastguard Worker // Compute half kernel values at half pixel steps out from the center.
92*c8dee2aaSAndroid Build Coastguard Worker float t = 0.5f;
93*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < halfKernelSize; ++i) {
94*c8dee2aaSAndroid Build Coastguard Worker float value = expf(t * t * b);
95*c8dee2aaSAndroid Build Coastguard Worker tot += value;
96*c8dee2aaSAndroid Build Coastguard Worker halfKernel[i] = value;
97*c8dee2aaSAndroid Build Coastguard Worker t += 1.0f;
98*c8dee2aaSAndroid Build Coastguard Worker }
99*c8dee2aaSAndroid Build Coastguard Worker return tot;
100*c8dee2aaSAndroid Build Coastguard Worker }
101*c8dee2aaSAndroid Build Coastguard Worker
102*c8dee2aaSAndroid Build Coastguard Worker // Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number
103*c8dee2aaSAndroid Build Coastguard Worker // of discrete steps. The half kernel is normalized to sum to 0.5.
make_half_kernel_and_summed_table(float * halfKernel,float * summedHalfKernel,int halfKernelSize,float sigma)104*c8dee2aaSAndroid Build Coastguard Worker static void make_half_kernel_and_summed_table(float* halfKernel,
105*c8dee2aaSAndroid Build Coastguard Worker float* summedHalfKernel,
106*c8dee2aaSAndroid Build Coastguard Worker int halfKernelSize,
107*c8dee2aaSAndroid Build Coastguard Worker float sigma) {
108*c8dee2aaSAndroid Build Coastguard Worker // The half kernel should sum to 0.5 not 1.0.
109*c8dee2aaSAndroid Build Coastguard Worker const float tot = 2.0f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma);
110*c8dee2aaSAndroid Build Coastguard Worker float sum = 0.0f;
111*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < halfKernelSize; ++i) {
112*c8dee2aaSAndroid Build Coastguard Worker halfKernel[i] /= tot;
113*c8dee2aaSAndroid Build Coastguard Worker sum += halfKernel[i];
114*c8dee2aaSAndroid Build Coastguard Worker summedHalfKernel[i] = sum;
115*c8dee2aaSAndroid Build Coastguard Worker }
116*c8dee2aaSAndroid Build Coastguard Worker }
117*c8dee2aaSAndroid Build Coastguard Worker
118*c8dee2aaSAndroid Build Coastguard Worker // Applies the 1D half kernel vertically at points along the x axis to a circle centered at the
119*c8dee2aaSAndroid Build Coastguard Worker // origin with radius circleR.
apply_kernel_in_y(float * results,int numSteps,float firstX,float circleR,int halfKernelSize,const float * summedHalfKernelTable)120*c8dee2aaSAndroid Build Coastguard Worker static void apply_kernel_in_y(float* results,
121*c8dee2aaSAndroid Build Coastguard Worker int numSteps,
122*c8dee2aaSAndroid Build Coastguard Worker float firstX,
123*c8dee2aaSAndroid Build Coastguard Worker float circleR,
124*c8dee2aaSAndroid Build Coastguard Worker int halfKernelSize,
125*c8dee2aaSAndroid Build Coastguard Worker const float* summedHalfKernelTable) {
126*c8dee2aaSAndroid Build Coastguard Worker float x = firstX;
127*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < numSteps; ++i, x += 1.0f) {
128*c8dee2aaSAndroid Build Coastguard Worker if (x < -circleR || x > circleR) {
129*c8dee2aaSAndroid Build Coastguard Worker results[i] = 0;
130*c8dee2aaSAndroid Build Coastguard Worker continue;
131*c8dee2aaSAndroid Build Coastguard Worker }
132*c8dee2aaSAndroid Build Coastguard Worker float y = sqrtf(circleR * circleR - x * x);
133*c8dee2aaSAndroid Build Coastguard Worker // In the column at x we exit the circle at +y and -y
134*c8dee2aaSAndroid Build Coastguard Worker // The summed table entry j is actually reflects an offset of j + 0.5.
135*c8dee2aaSAndroid Build Coastguard Worker y -= 0.5f;
136*c8dee2aaSAndroid Build Coastguard Worker int yInt = SkScalarFloorToInt(y);
137*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(yInt >= -1);
138*c8dee2aaSAndroid Build Coastguard Worker if (y < 0) {
139*c8dee2aaSAndroid Build Coastguard Worker results[i] = (y + 0.5f) * summedHalfKernelTable[0];
140*c8dee2aaSAndroid Build Coastguard Worker } else if (yInt >= halfKernelSize - 1) {
141*c8dee2aaSAndroid Build Coastguard Worker results[i] = 0.5f;
142*c8dee2aaSAndroid Build Coastguard Worker } else {
143*c8dee2aaSAndroid Build Coastguard Worker float yFrac = y - yInt;
144*c8dee2aaSAndroid Build Coastguard Worker results[i] = (1.0f - yFrac) * summedHalfKernelTable[yInt] +
145*c8dee2aaSAndroid Build Coastguard Worker yFrac * summedHalfKernelTable[yInt + 1];
146*c8dee2aaSAndroid Build Coastguard Worker }
147*c8dee2aaSAndroid Build Coastguard Worker }
148*c8dee2aaSAndroid Build Coastguard Worker }
149*c8dee2aaSAndroid Build Coastguard Worker
150*c8dee2aaSAndroid Build Coastguard Worker // Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR.
151*c8dee2aaSAndroid Build Coastguard Worker // This relies on having a half kernel computed for the Gaussian and a table of applications of
152*c8dee2aaSAndroid Build Coastguard Worker // the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX +
153*c8dee2aaSAndroid Build Coastguard Worker // halfKernel) passed in as yKernelEvaluations.
eval_at(float evalX,float circleR,const float * halfKernel,int halfKernelSize,const float * yKernelEvaluations)154*c8dee2aaSAndroid Build Coastguard Worker static uint8_t eval_at(float evalX,
155*c8dee2aaSAndroid Build Coastguard Worker float circleR,
156*c8dee2aaSAndroid Build Coastguard Worker const float* halfKernel,
157*c8dee2aaSAndroid Build Coastguard Worker int halfKernelSize,
158*c8dee2aaSAndroid Build Coastguard Worker const float* yKernelEvaluations) {
159*c8dee2aaSAndroid Build Coastguard Worker float acc = 0;
160*c8dee2aaSAndroid Build Coastguard Worker
161*c8dee2aaSAndroid Build Coastguard Worker float x = evalX - halfKernelSize;
162*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < halfKernelSize; ++i, x += 1.0f) {
163*c8dee2aaSAndroid Build Coastguard Worker if (x < -circleR || x > circleR) {
164*c8dee2aaSAndroid Build Coastguard Worker continue;
165*c8dee2aaSAndroid Build Coastguard Worker }
166*c8dee2aaSAndroid Build Coastguard Worker float verticalEval = yKernelEvaluations[i];
167*c8dee2aaSAndroid Build Coastguard Worker acc += verticalEval * halfKernel[halfKernelSize - i - 1];
168*c8dee2aaSAndroid Build Coastguard Worker }
169*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < halfKernelSize; ++i, x += 1.0f) {
170*c8dee2aaSAndroid Build Coastguard Worker if (x < -circleR || x > circleR) {
171*c8dee2aaSAndroid Build Coastguard Worker continue;
172*c8dee2aaSAndroid Build Coastguard Worker }
173*c8dee2aaSAndroid Build Coastguard Worker float verticalEval = yKernelEvaluations[i + halfKernelSize];
174*c8dee2aaSAndroid Build Coastguard Worker acc += verticalEval * halfKernel[i];
175*c8dee2aaSAndroid Build Coastguard Worker }
176*c8dee2aaSAndroid Build Coastguard Worker // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about
177*c8dee2aaSAndroid Build Coastguard Worker // the x axis).
178*c8dee2aaSAndroid Build Coastguard Worker return SkUnitScalarClampToByte(2.0f * acc);
179*c8dee2aaSAndroid Build Coastguard Worker }
180*c8dee2aaSAndroid Build Coastguard Worker
181*c8dee2aaSAndroid Build Coastguard Worker // This function creates a profile of a blurred circle. It does this by computing a kernel for
182*c8dee2aaSAndroid Build Coastguard Worker // half the Gaussian and a matching summed area table. The summed area table is used to compute
183*c8dee2aaSAndroid Build Coastguard Worker // an array of vertical applications of the half kernel to the circle along the x axis. The
184*c8dee2aaSAndroid Build Coastguard Worker // table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is
185*c8dee2aaSAndroid Build Coastguard Worker // the size of the profile being computed. Then for each of the n profile entries we walk out k
186*c8dee2aaSAndroid Build Coastguard Worker // steps in each horizontal direction multiplying the corresponding y evaluation by the half
187*c8dee2aaSAndroid Build Coastguard Worker // kernel entry and sum these values to compute the profile entry.
CreateCircleProfile(float sigma,float radius,int profileWidth)188*c8dee2aaSAndroid Build Coastguard Worker SkBitmap CreateCircleProfile(float sigma, float radius, int profileWidth) {
189*c8dee2aaSAndroid Build Coastguard Worker SkBitmap bitmap;
190*c8dee2aaSAndroid Build Coastguard Worker if (!bitmap.tryAllocPixels(SkImageInfo::MakeA8(profileWidth, 1))) {
191*c8dee2aaSAndroid Build Coastguard Worker return bitmap;
192*c8dee2aaSAndroid Build Coastguard Worker }
193*c8dee2aaSAndroid Build Coastguard Worker
194*c8dee2aaSAndroid Build Coastguard Worker uint8_t* profile = bitmap.getAddr8(0, 0);
195*c8dee2aaSAndroid Build Coastguard Worker
196*c8dee2aaSAndroid Build Coastguard Worker const int numSteps = profileWidth;
197*c8dee2aaSAndroid Build Coastguard Worker
198*c8dee2aaSAndroid Build Coastguard Worker // The full kernel is 6 sigmas wide.
199*c8dee2aaSAndroid Build Coastguard Worker int halfKernelSize = SkScalarCeilToInt(6.0f * sigma);
200*c8dee2aaSAndroid Build Coastguard Worker // Round up to next multiple of 2 and then divide by 2.
201*c8dee2aaSAndroid Build Coastguard Worker halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
202*c8dee2aaSAndroid Build Coastguard Worker
203*c8dee2aaSAndroid Build Coastguard Worker // Number of x steps at which to apply kernel in y to cover all the profile samples in x.
204*c8dee2aaSAndroid Build Coastguard Worker const int numYSteps = numSteps + 2 * halfKernelSize;
205*c8dee2aaSAndroid Build Coastguard Worker
206*c8dee2aaSAndroid Build Coastguard Worker skia_private::AutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps);
207*c8dee2aaSAndroid Build Coastguard Worker float* halfKernel = bulkAlloc.get();
208*c8dee2aaSAndroid Build Coastguard Worker float* summedKernel = bulkAlloc.get() + halfKernelSize;
209*c8dee2aaSAndroid Build Coastguard Worker float* yEvals = bulkAlloc.get() + 2 * halfKernelSize;
210*c8dee2aaSAndroid Build Coastguard Worker make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma);
211*c8dee2aaSAndroid Build Coastguard Worker
212*c8dee2aaSAndroid Build Coastguard Worker float firstX = -halfKernelSize + 0.5f;
213*c8dee2aaSAndroid Build Coastguard Worker apply_kernel_in_y(yEvals, numYSteps, firstX, radius, halfKernelSize, summedKernel);
214*c8dee2aaSAndroid Build Coastguard Worker
215*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < numSteps - 1; ++i) {
216*c8dee2aaSAndroid Build Coastguard Worker float evalX = i + 0.5f;
217*c8dee2aaSAndroid Build Coastguard Worker profile[i] = eval_at(evalX, radius, halfKernel, halfKernelSize, yEvals + i);
218*c8dee2aaSAndroid Build Coastguard Worker }
219*c8dee2aaSAndroid Build Coastguard Worker // Ensure the tail of the Gaussian goes to zero.
220*c8dee2aaSAndroid Build Coastguard Worker profile[numSteps - 1] = 0;
221*c8dee2aaSAndroid Build Coastguard Worker
222*c8dee2aaSAndroid Build Coastguard Worker bitmap.setImmutable();
223*c8dee2aaSAndroid Build Coastguard Worker return bitmap;
224*c8dee2aaSAndroid Build Coastguard Worker }
225*c8dee2aaSAndroid Build Coastguard Worker
CreateHalfPlaneProfile(int profileWidth)226*c8dee2aaSAndroid Build Coastguard Worker SkBitmap CreateHalfPlaneProfile(int profileWidth) {
227*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!(profileWidth & 0x1));
228*c8dee2aaSAndroid Build Coastguard Worker
229*c8dee2aaSAndroid Build Coastguard Worker SkBitmap bitmap;
230*c8dee2aaSAndroid Build Coastguard Worker if (!bitmap.tryAllocPixels(SkImageInfo::MakeA8(profileWidth, 1))) {
231*c8dee2aaSAndroid Build Coastguard Worker return bitmap;
232*c8dee2aaSAndroid Build Coastguard Worker }
233*c8dee2aaSAndroid Build Coastguard Worker
234*c8dee2aaSAndroid Build Coastguard Worker uint8_t* profile = bitmap.getAddr8(0, 0);
235*c8dee2aaSAndroid Build Coastguard Worker
236*c8dee2aaSAndroid Build Coastguard Worker // The full kernel is 6 sigmas wide.
237*c8dee2aaSAndroid Build Coastguard Worker const float sigma = profileWidth / 6.0f;
238*c8dee2aaSAndroid Build Coastguard Worker const int halfKernelSize = profileWidth / 2;
239*c8dee2aaSAndroid Build Coastguard Worker
240*c8dee2aaSAndroid Build Coastguard Worker skia_private::AutoTArray<float> halfKernel(halfKernelSize);
241*c8dee2aaSAndroid Build Coastguard Worker
242*c8dee2aaSAndroid Build Coastguard Worker // The half kernel should sum to 0.5.
243*c8dee2aaSAndroid Build Coastguard Worker const float tot = 2.0f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, sigma);
244*c8dee2aaSAndroid Build Coastguard Worker float sum = 0.0f;
245*c8dee2aaSAndroid Build Coastguard Worker // Populate the profile from the right edge to the middle.
246*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < halfKernelSize; ++i) {
247*c8dee2aaSAndroid Build Coastguard Worker halfKernel[halfKernelSize - i - 1] /= tot;
248*c8dee2aaSAndroid Build Coastguard Worker sum += halfKernel[halfKernelSize - i - 1];
249*c8dee2aaSAndroid Build Coastguard Worker profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum);
250*c8dee2aaSAndroid Build Coastguard Worker }
251*c8dee2aaSAndroid Build Coastguard Worker // Populate the profile from the middle to the left edge (by flipping the half kernel and
252*c8dee2aaSAndroid Build Coastguard Worker // continuing the summation).
253*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < halfKernelSize; ++i) {
254*c8dee2aaSAndroid Build Coastguard Worker sum += halfKernel[i];
255*c8dee2aaSAndroid Build Coastguard Worker profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum);
256*c8dee2aaSAndroid Build Coastguard Worker }
257*c8dee2aaSAndroid Build Coastguard Worker // Ensure the tail of the Gaussian goes to zero.
258*c8dee2aaSAndroid Build Coastguard Worker profile[profileWidth - 1] = 0;
259*c8dee2aaSAndroid Build Coastguard Worker
260*c8dee2aaSAndroid Build Coastguard Worker bitmap.setImmutable();
261*c8dee2aaSAndroid Build Coastguard Worker return bitmap;
262*c8dee2aaSAndroid Build Coastguard Worker }
263*c8dee2aaSAndroid Build Coastguard Worker
264*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
265*c8dee2aaSAndroid Build Coastguard Worker // RRect Blur
266*c8dee2aaSAndroid Build Coastguard Worker ///////////////////////////////////////////////////////////////////////////////
267*c8dee2aaSAndroid Build Coastguard Worker
268*c8dee2aaSAndroid Build Coastguard Worker // Evaluate the vertical blur at the specified 'y' value given the location of the top of the
269*c8dee2aaSAndroid Build Coastguard Worker // rrect.
eval_V(float top,int y,const uint8_t * integral,int integralSize,float sixSigma)270*c8dee2aaSAndroid Build Coastguard Worker static uint8_t eval_V(float top, int y, const uint8_t* integral, int integralSize, float sixSigma) {
271*c8dee2aaSAndroid Build Coastguard Worker if (top < 0) {
272*c8dee2aaSAndroid Build Coastguard Worker return 0; // an empty column
273*c8dee2aaSAndroid Build Coastguard Worker }
274*c8dee2aaSAndroid Build Coastguard Worker
275*c8dee2aaSAndroid Build Coastguard Worker float fT = (top - y - 0.5f) * (integralSize / sixSigma);
276*c8dee2aaSAndroid Build Coastguard Worker if (fT < 0) {
277*c8dee2aaSAndroid Build Coastguard Worker return 255;
278*c8dee2aaSAndroid Build Coastguard Worker } else if (fT >= integralSize - 1) {
279*c8dee2aaSAndroid Build Coastguard Worker return 0;
280*c8dee2aaSAndroid Build Coastguard Worker }
281*c8dee2aaSAndroid Build Coastguard Worker
282*c8dee2aaSAndroid Build Coastguard Worker int lower = (int)fT;
283*c8dee2aaSAndroid Build Coastguard Worker float frac = fT - lower;
284*c8dee2aaSAndroid Build Coastguard Worker
285*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(lower + 1 < integralSize);
286*c8dee2aaSAndroid Build Coastguard Worker
287*c8dee2aaSAndroid Build Coastguard Worker return integral[lower] * (1.0f - frac) + integral[lower + 1] * frac;
288*c8dee2aaSAndroid Build Coastguard Worker }
289*c8dee2aaSAndroid Build Coastguard Worker
290*c8dee2aaSAndroid Build Coastguard Worker // Apply a gaussian 'kernel' horizontally at the specified 'x', 'y' location.
eval_H(int x,int y,const std::vector<float> & topVec,const float * kernel,int kernelSize,const uint8_t * integral,int integralSize,float sixSigma)291*c8dee2aaSAndroid Build Coastguard Worker static uint8_t eval_H(int x,
292*c8dee2aaSAndroid Build Coastguard Worker int y,
293*c8dee2aaSAndroid Build Coastguard Worker const std::vector<float>& topVec,
294*c8dee2aaSAndroid Build Coastguard Worker const float* kernel,
295*c8dee2aaSAndroid Build Coastguard Worker int kernelSize,
296*c8dee2aaSAndroid Build Coastguard Worker const uint8_t* integral,
297*c8dee2aaSAndroid Build Coastguard Worker int integralSize,
298*c8dee2aaSAndroid Build Coastguard Worker float sixSigma) {
299*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(0 <= x && x < (int)topVec.size());
300*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(kernelSize % 2);
301*c8dee2aaSAndroid Build Coastguard Worker
302*c8dee2aaSAndroid Build Coastguard Worker float accum = 0.0f;
303*c8dee2aaSAndroid Build Coastguard Worker
304*c8dee2aaSAndroid Build Coastguard Worker int xSampleLoc = x - (kernelSize / 2);
305*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < kernelSize; ++i, ++xSampleLoc) {
306*c8dee2aaSAndroid Build Coastguard Worker if (xSampleLoc < 0 || xSampleLoc >= (int)topVec.size()) {
307*c8dee2aaSAndroid Build Coastguard Worker continue;
308*c8dee2aaSAndroid Build Coastguard Worker }
309*c8dee2aaSAndroid Build Coastguard Worker
310*c8dee2aaSAndroid Build Coastguard Worker accum += kernel[i] * eval_V(topVec[xSampleLoc], y, integral, integralSize, sixSigma);
311*c8dee2aaSAndroid Build Coastguard Worker }
312*c8dee2aaSAndroid Build Coastguard Worker
313*c8dee2aaSAndroid Build Coastguard Worker return accum + 0.5f;
314*c8dee2aaSAndroid Build Coastguard Worker }
315*c8dee2aaSAndroid Build Coastguard Worker
CreateRRectBlurMask(const SkRRect & rrectToDraw,const SkISize & dimensions,float sigma)316*c8dee2aaSAndroid Build Coastguard Worker SkBitmap CreateRRectBlurMask(const SkRRect& rrectToDraw, const SkISize& dimensions, float sigma) {
317*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!skgpu::BlurIsEffectivelyIdentity(sigma));
318*c8dee2aaSAndroid Build Coastguard Worker int radius = skgpu::BlurSigmaRadius(sigma);
319*c8dee2aaSAndroid Build Coastguard Worker int kernelSize = skgpu::BlurKernelWidth(radius);
320*c8dee2aaSAndroid Build Coastguard Worker
321*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(kernelSize % 2);
322*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(dimensions.width() % 2);
323*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(dimensions.height() % 2);
324*c8dee2aaSAndroid Build Coastguard Worker
325*c8dee2aaSAndroid Build Coastguard Worker SkVector radii = rrectToDraw.getSimpleRadii();
326*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(SkScalarNearlyEqual(radii.fX, radii.fY));
327*c8dee2aaSAndroid Build Coastguard Worker
328*c8dee2aaSAndroid Build Coastguard Worker const int halfWidthPlus1 = (dimensions.width() / 2) + 1;
329*c8dee2aaSAndroid Build Coastguard Worker const int halfHeightPlus1 = (dimensions.height() / 2) + 1;
330*c8dee2aaSAndroid Build Coastguard Worker
331*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<float[]> kernel(new float[kernelSize]);
332*c8dee2aaSAndroid Build Coastguard Worker skgpu::Compute1DBlurKernel(sigma, radius, SkSpan<float>(kernel.get(), kernelSize));
333*c8dee2aaSAndroid Build Coastguard Worker
334*c8dee2aaSAndroid Build Coastguard Worker const int tableWidth = ComputeIntegralTableWidth(6.0f * sigma);
335*c8dee2aaSAndroid Build Coastguard Worker SkBitmap integral = CreateIntegralTable(tableWidth);
336*c8dee2aaSAndroid Build Coastguard Worker if (integral.empty()) {
337*c8dee2aaSAndroid Build Coastguard Worker return {};
338*c8dee2aaSAndroid Build Coastguard Worker }
339*c8dee2aaSAndroid Build Coastguard Worker
340*c8dee2aaSAndroid Build Coastguard Worker SkBitmap result;
341*c8dee2aaSAndroid Build Coastguard Worker if (!result.tryAllocPixels(SkImageInfo::MakeA8(dimensions.width(), dimensions.height()))) {
342*c8dee2aaSAndroid Build Coastguard Worker return {};
343*c8dee2aaSAndroid Build Coastguard Worker }
344*c8dee2aaSAndroid Build Coastguard Worker
345*c8dee2aaSAndroid Build Coastguard Worker std::vector<float> topVec;
346*c8dee2aaSAndroid Build Coastguard Worker topVec.reserve(dimensions.width());
347*c8dee2aaSAndroid Build Coastguard Worker for (int x = 0; x < dimensions.width(); ++x) {
348*c8dee2aaSAndroid Build Coastguard Worker if (x < rrectToDraw.rect().fLeft || x > rrectToDraw.rect().fRight) {
349*c8dee2aaSAndroid Build Coastguard Worker topVec.push_back(-1);
350*c8dee2aaSAndroid Build Coastguard Worker } else {
351*c8dee2aaSAndroid Build Coastguard Worker if (x + 0.5f < rrectToDraw.rect().fLeft + radii.fX) { // in the circular section
352*c8dee2aaSAndroid Build Coastguard Worker float xDist = rrectToDraw.rect().fLeft + radii.fX - x - 0.5f;
353*c8dee2aaSAndroid Build Coastguard Worker float h = sqrtf(radii.fX * radii.fX - xDist * xDist);
354*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(0 <= h && h < radii.fY);
355*c8dee2aaSAndroid Build Coastguard Worker topVec.push_back(rrectToDraw.rect().fTop + radii.fX - h + 3 * sigma);
356*c8dee2aaSAndroid Build Coastguard Worker } else {
357*c8dee2aaSAndroid Build Coastguard Worker topVec.push_back(rrectToDraw.rect().fTop + 3 * sigma);
358*c8dee2aaSAndroid Build Coastguard Worker }
359*c8dee2aaSAndroid Build Coastguard Worker }
360*c8dee2aaSAndroid Build Coastguard Worker }
361*c8dee2aaSAndroid Build Coastguard Worker
362*c8dee2aaSAndroid Build Coastguard Worker for (int y = 0; y < halfHeightPlus1; ++y) {
363*c8dee2aaSAndroid Build Coastguard Worker uint8_t* scanline = result.getAddr8(0, y);
364*c8dee2aaSAndroid Build Coastguard Worker
365*c8dee2aaSAndroid Build Coastguard Worker for (int x = 0; x < halfWidthPlus1; ++x) {
366*c8dee2aaSAndroid Build Coastguard Worker scanline[x] = eval_H(x,
367*c8dee2aaSAndroid Build Coastguard Worker y,
368*c8dee2aaSAndroid Build Coastguard Worker topVec,
369*c8dee2aaSAndroid Build Coastguard Worker kernel.get(),
370*c8dee2aaSAndroid Build Coastguard Worker kernelSize,
371*c8dee2aaSAndroid Build Coastguard Worker integral.getAddr8(0, 0),
372*c8dee2aaSAndroid Build Coastguard Worker integral.width(),
373*c8dee2aaSAndroid Build Coastguard Worker 6.0f * sigma);
374*c8dee2aaSAndroid Build Coastguard Worker scanline[dimensions.width() - x - 1] = scanline[x];
375*c8dee2aaSAndroid Build Coastguard Worker }
376*c8dee2aaSAndroid Build Coastguard Worker
377*c8dee2aaSAndroid Build Coastguard Worker memcpy(result.getAddr8(0, dimensions.height() - y - 1), scanline, result.rowBytes());
378*c8dee2aaSAndroid Build Coastguard Worker }
379*c8dee2aaSAndroid Build Coastguard Worker
380*c8dee2aaSAndroid Build Coastguard Worker result.setImmutable();
381*c8dee2aaSAndroid Build Coastguard Worker return result;
382*c8dee2aaSAndroid Build Coastguard Worker }
383*c8dee2aaSAndroid Build Coastguard Worker
384*c8dee2aaSAndroid Build Coastguard Worker } // namespace skgpu
385