1 | /* |
2 | * Copyright 2013 Google Inc. |
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 | |
8 | #include "src/core/SkGpuBlurUtils.h" |
9 | |
10 | #include "include/core/SkRect.h" |
11 | |
12 | #if SK_SUPPORT_GPU |
13 | #include "include/gpu/GrRecordingContext.h" |
14 | #include "src/gpu/GrCaps.h" |
15 | #include "src/gpu/GrRecordingContextPriv.h" |
16 | #include "src/gpu/GrRenderTargetContext.h" |
17 | #include "src/gpu/GrRenderTargetContextPriv.h" |
18 | #include "src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h" |
19 | #include "src/gpu/effects/GrMatrixConvolutionEffect.h" |
20 | |
21 | #include "src/gpu/SkGr.h" |
22 | |
23 | #define MAX_BLUR_SIGMA 4.0f |
24 | |
25 | using Direction = GrGaussianConvolutionFragmentProcessor::Direction; |
26 | |
27 | static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) { |
28 | rect->fLeft = SkScalarFloorToInt(rect->fLeft * xScale); |
29 | rect->fTop = SkScalarFloorToInt(rect->fTop * yScale); |
30 | rect->fRight = SkScalarCeilToInt(rect->fRight * xScale); |
31 | rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale); |
32 | } |
33 | |
34 | static void scale_irect(SkIRect* rect, int xScale, int yScale) { |
35 | rect->fLeft *= xScale; |
36 | rect->fTop *= yScale; |
37 | rect->fRight *= xScale; |
38 | rect->fBottom *= yScale; |
39 | } |
40 | |
41 | #ifdef SK_DEBUG |
42 | static inline int is_even(int x) { return !(x & 1); } |
43 | #endif |
44 | |
45 | static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) { |
46 | if (xAxis) { |
47 | SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight)); |
48 | rect->fLeft /= 2; |
49 | rect->fRight /= 2; |
50 | } |
51 | if (yAxis) { |
52 | SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom)); |
53 | rect->fTop /= 2; |
54 | rect->fBottom /= 2; |
55 | } |
56 | } |
57 | |
58 | static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) { |
59 | *scaleFactor = 1; |
60 | while (sigma > MAX_BLUR_SIGMA) { |
61 | *scaleFactor *= 2; |
62 | sigma *= 0.5f; |
63 | if (*scaleFactor > maxTextureSize) { |
64 | *scaleFactor = maxTextureSize; |
65 | sigma = MAX_BLUR_SIGMA; |
66 | } |
67 | } |
68 | *radius = static_cast<int>(ceilf(sigma * 3.0f)); |
69 | SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius); |
70 | return sigma; |
71 | } |
72 | |
73 | /** |
74 | * Draws 'rtcRect' into 'renderTargetContext' evaluating a 1D Gaussian over 'srcView'. The src rect |
75 | * is 'rtcRect' offset by 'rtcToSrcOffset'. 'mode' and 'bounds' are applied to the src coords. |
76 | */ |
77 | static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext, |
78 | GrSurfaceProxyView srcView, |
79 | SkIVector rtcToSrcOffset, |
80 | const SkIRect& rtcRect, |
81 | SkAlphaType srcAlphaType, |
82 | Direction direction, |
83 | int radius, |
84 | float sigma, |
85 | SkTileMode mode, |
86 | int bounds[2]) { |
87 | GrPaint paint; |
88 | auto wm = SkTileModeToWrapMode(mode); |
89 | auto subset = SkIRect::MakeSize(srcView.dimensions()); |
90 | if (bounds) { |
91 | switch (direction) { |
92 | case Direction::kX: subset.fLeft = bounds[0]; subset.fRight = bounds[1]; break; |
93 | case Direction::kY: subset.fTop = bounds[0]; subset.fBottom = bounds[1]; break; |
94 | } |
95 | } |
96 | std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make( |
97 | std::move(srcView), srcAlphaType, direction, radius, sigma, wm, subset, nullptr, |
98 | *renderTargetContext->caps())); |
99 | paint.setColorFragmentProcessor(std::move(conv)); |
100 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
101 | auto srcRect = SkRect::Make(rtcRect.makeOffset(rtcToSrcOffset)); |
102 | renderTargetContext->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(), |
103 | SkRect::Make(rtcRect), srcRect); |
104 | } |
105 | |
106 | static std::unique_ptr<GrRenderTargetContext> convolve_gaussian_2d(GrRecordingContext* context, |
107 | GrSurfaceProxyView srcView, |
108 | GrColorType srcColorType, |
109 | const SkIRect& srcBounds, |
110 | const SkIRect& dstBounds, |
111 | int radiusX, |
112 | int radiusY, |
113 | SkScalar sigmaX, |
114 | SkScalar sigmaY, |
115 | SkTileMode mode, |
116 | sk_sp<SkColorSpace> finalCS, |
117 | SkBackingFit dstFit) { |
118 | auto renderTargetContext = GrRenderTargetContext::Make( |
119 | context, srcColorType, std::move(finalCS), dstFit, dstBounds.size(), 1, |
120 | GrMipmapped::kNo, srcView.proxy()->isProtected(), srcView.origin()); |
121 | if (!renderTargetContext) { |
122 | return nullptr; |
123 | } |
124 | |
125 | SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); |
126 | SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); |
127 | GrPaint paint; |
128 | auto wm = SkTileModeToWrapMode(mode); |
129 | auto conv = GrMatrixConvolutionEffect::MakeGaussian(context, std::move(srcView), srcBounds, |
130 | size, 1.0, 0.0, kernelOffset, wm, true, |
131 | sigmaX, sigmaY, |
132 | *renderTargetContext->caps()); |
133 | paint.setColorFragmentProcessor(std::move(conv)); |
134 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
135 | |
136 | // 'dstBounds' is actually in 'srcView' proxy space. It represents the blurred area from src |
137 | // space that we want to capture in the new RTC at {0, 0}. Hence, we use its size as the rect to |
138 | // draw and it directly as the local rect. |
139 | renderTargetContext->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(), |
140 | SkRect::Make(dstBounds.size()), SkRect::Make(dstBounds)); |
141 | |
142 | return renderTargetContext; |
143 | } |
144 | |
145 | static std::unique_ptr<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* context, |
146 | GrSurfaceProxyView srcView, |
147 | GrColorType srcColorType, |
148 | SkAlphaType srcAlphaType, |
149 | SkIRect* contentRect, |
150 | SkIRect dstBounds, |
151 | Direction direction, |
152 | int radius, |
153 | float sigma, |
154 | SkTileMode mode, |
155 | sk_sp<SkColorSpace> finalCS, |
156 | SkBackingFit fit) { |
157 | // Logically we're creating an infinite blur of 'contentRect' of 'srcView' with 'mode' tiling |
158 | // and then capturing the 'dstBounds' portion in a new RTC where the top left of 'dstBounds' is |
159 | // at {0, 0} in the new RTC. |
160 | auto dstRenderTargetContext = GrRenderTargetContext::Make( |
161 | context, srcColorType, std::move(finalCS), fit, dstBounds.size(), 1, GrMipmapped::kNo, |
162 | srcView.proxy()->isProtected(), srcView.origin()); |
163 | if (!dstRenderTargetContext) { |
164 | return nullptr; |
165 | } |
166 | |
167 | // This represents the translation from 'dstRenderTargetContext' coords to 'srcView' coords. |
168 | auto rtcToSrcOffset = dstBounds.topLeft(); |
169 | |
170 | if (SkTileMode::kClamp == mode && |
171 | contentRect->contains(SkIRect::MakeSize(srcView.proxy()->backingStoreDimensions()))) { |
172 | auto dstRect = SkIRect::MakeSize(dstBounds.size()); |
173 | convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset, |
174 | dstRect, srcAlphaType, direction, radius, sigma, SkTileMode::kClamp, |
175 | nullptr); |
176 | *contentRect = dstRect; |
177 | return dstRenderTargetContext; |
178 | } |
179 | |
180 | // 'left' and 'right' are the sub rects of 'contentTect' where 'mode' must be enforced. |
181 | // 'mid' is the area where we can ignore the mode because the kernel does not reach to the |
182 | // edge of 'contentRect'. The names are derived from the Direction::kX case. |
183 | // TODO: When mode is kMirror or kRepeat it makes more sense to think of 'contentRect' |
184 | // as a tile and figure out the collection of mid/left/right rects that cover 'dstBounds'. |
185 | // Also if 'mid' is small and 'left' or 'right' is non-empty we should probably issue one |
186 | // draw that implements the mode in the shader rather than break it up in this fashion. |
187 | SkIRect mid, left, right; |
188 | // 'top' and 'bottom' are areas of 'dstBounds' that are entirely above/below |
189 | // 'contentRect'. These are areas that we can simply clear in the dst. If 'contentRect' |
190 | // straddles the top edge of 'dstBounds' then 'top' will be inverted and we will skip |
191 | // the clear. Similar for 'bottom'. The positional/directional labels above refer to the |
192 | // Direction::kX case and one should think of these as 'left' and 'right' for Direction::kY. |
193 | SkIRect top, bottom; |
194 | int bounds[2]; |
195 | if (Direction::kX == direction) { |
196 | bounds[0] = contentRect->left(); |
197 | bounds[1] = contentRect->right(); |
198 | |
199 | top = {dstBounds.left(), dstBounds.top() , dstBounds.right(), contentRect->top()}; |
200 | bottom = {dstBounds.left(), contentRect->bottom(), dstBounds.right(), dstBounds.bottom()}; |
201 | |
202 | // Inset for sub-rect of 'contentRect' where the x-dir kernel doesn't reach the edges. |
203 | // TODO: Consider clipping mid/left/right to dstBounds to increase likelihood of doing |
204 | // fewer draws below. |
205 | mid = contentRect->makeInset(radius, 0); |
206 | |
207 | left = {dstBounds.left(), mid.top(), mid.left() , mid.bottom()}; |
208 | right = {mid.right(), mid.top(), dstBounds.right(), mid.bottom()}; |
209 | |
210 | // The new 'contentRect' when we're done will be the area between the clears in the dst. |
211 | *contentRect = {dstBounds.left(), |
212 | std::max(contentRect->top(), dstBounds.top()), |
213 | dstBounds.right(), |
214 | std::min(dstBounds.bottom(), contentRect->bottom())}; |
215 | } else { |
216 | // This is the same as the x direction code if you turn your head 90 degrees CCW. Swap x and |
217 | // y and swap top/bottom with left/right. |
218 | bounds[0] = contentRect->top(); |
219 | bounds[1] = contentRect->bottom(); |
220 | |
221 | top = {dstBounds.left(), dstBounds.top() , contentRect->left(), dstBounds.bottom()}; |
222 | bottom = {contentRect->right(), dstBounds.top() , dstBounds.right() , dstBounds.bottom()}; |
223 | |
224 | mid = contentRect->makeInset(0, radius); |
225 | |
226 | left = {mid.left(), dstBounds.top(), mid.right(), mid.top() }; |
227 | right = {mid.left(), mid.bottom() , mid.right(), dstBounds.bottom()}; |
228 | |
229 | *contentRect = {std::max(contentRect->left(), dstBounds.left()), |
230 | dstBounds.top(), |
231 | std::min(contentRect->right(), dstBounds.right()), |
232 | dstBounds.bottom()}; |
233 | } |
234 | // Move all the rects from 'srcView' coord system to 'dstRenderTargetContext' coord system. |
235 | mid .offset(-rtcToSrcOffset); |
236 | top .offset(-rtcToSrcOffset); |
237 | bottom.offset(-rtcToSrcOffset); |
238 | left .offset(-rtcToSrcOffset); |
239 | right .offset(-rtcToSrcOffset); |
240 | |
241 | contentRect->offset(-rtcToSrcOffset); |
242 | |
243 | if (!top.isEmpty()) { |
244 | dstRenderTargetContext->priv().clearAtLeast(top, SK_PMColor4fTRANSPARENT); |
245 | } |
246 | |
247 | if (!bottom.isEmpty()) { |
248 | dstRenderTargetContext->priv().clearAtLeast(bottom, SK_PMColor4fTRANSPARENT); |
249 | } |
250 | |
251 | if (mid.isEmpty()) { |
252 | convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset, |
253 | *contentRect, srcAlphaType, direction, radius, sigma, mode, bounds); |
254 | } else { |
255 | // Draw right and left margins with bounds; middle without. |
256 | convolve_gaussian_1d(dstRenderTargetContext.get(), srcView, rtcToSrcOffset, left, |
257 | srcAlphaType, direction, radius, sigma, mode, bounds); |
258 | convolve_gaussian_1d(dstRenderTargetContext.get(), srcView, rtcToSrcOffset, right, |
259 | srcAlphaType, direction, radius, sigma, mode, bounds); |
260 | convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset, mid, |
261 | srcAlphaType, direction, radius, sigma, SkTileMode::kClamp, nullptr); |
262 | } |
263 | |
264 | return dstRenderTargetContext; |
265 | } |
266 | |
267 | // Returns a high quality scaled-down version of src. This is used to create an intermediate, |
268 | // shrunken version of the source image in the event that the requested blur sigma exceeds |
269 | // MAX_BLUR_SIGMA. |
270 | static GrSurfaceProxyView decimate(GrRecordingContext* context, |
271 | GrSurfaceProxyView srcView, |
272 | GrColorType srcColorType, |
273 | SkAlphaType srcAlphaType, |
274 | SkIPoint srcOffset, |
275 | SkIRect* contentRect, |
276 | int scaleFactorX, |
277 | int scaleFactorY, |
278 | SkTileMode mode, |
279 | sk_sp<SkColorSpace> finalCS) { |
280 | SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY)); |
281 | SkASSERT(scaleFactorX > 1 || scaleFactorY > 1); |
282 | |
283 | SkIRect srcRect = contentRect->makeOffset(srcOffset); |
284 | |
285 | scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); |
286 | scale_irect(&srcRect, scaleFactorX, scaleFactorY); |
287 | |
288 | SkIRect dstRect(srcRect); |
289 | |
290 | std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext; |
291 | |
292 | for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { |
293 | shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY); |
294 | |
295 | dstRenderTargetContext = GrRenderTargetContext::Make( |
296 | context, srcColorType, finalCS, SkBackingFit::kApprox, |
297 | {dstRect.fRight, dstRect.fBottom}, 1, GrMipmapped::kNo, |
298 | srcView.proxy()->isProtected(), srcView.origin()); |
299 | if (!dstRenderTargetContext) { |
300 | return {}; |
301 | } |
302 | |
303 | GrPaint paint; |
304 | std::unique_ptr<GrFragmentProcessor> fp; |
305 | if (i == 1) { |
306 | GrSamplerState::WrapMode wrapMode = SkTileModeToWrapMode(mode); |
307 | const auto& caps = *context->priv().caps(); |
308 | GrSamplerState sampler(wrapMode, GrSamplerState::Filter::kLinear); |
309 | fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(), |
310 | sampler, SkRect::Make(*contentRect), caps); |
311 | srcRect.offset(-srcOffset); |
312 | } else { |
313 | fp = GrTextureEffect::Make(std::move(srcView), srcAlphaType, SkMatrix::I(), |
314 | GrSamplerState::Filter::kLinear); |
315 | } |
316 | paint.setColorFragmentProcessor(std::move(fp)); |
317 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
318 | |
319 | dstRenderTargetContext->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, |
320 | SkMatrix::I(), SkRect::Make(dstRect), |
321 | SkRect::Make(srcRect)); |
322 | |
323 | srcView = dstRenderTargetContext->readSurfaceView(); |
324 | if (!srcView.asTextureProxy()) { |
325 | return {}; |
326 | } |
327 | srcRect = dstRect; |
328 | } |
329 | |
330 | *contentRect = dstRect; |
331 | |
332 | SkASSERT(dstRenderTargetContext); |
333 | SkASSERT(srcView == dstRenderTargetContext->readSurfaceView()); |
334 | |
335 | return srcView; |
336 | } |
337 | |
338 | // Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'. At this point, we are |
339 | // expanding an intermediate image, so there's no need to account for a proxy offset from the |
340 | // original input. |
341 | static std::unique_ptr<GrRenderTargetContext> reexpand(GrRecordingContext* context, |
342 | std::unique_ptr<GrRenderTargetContext> src, |
343 | const SkIRect& srcBounds, |
344 | int scaleFactorX, |
345 | int scaleFactorY, |
346 | SkISize dstSize, |
347 | sk_sp<SkColorSpace> colorSpace, |
348 | SkBackingFit fit) { |
349 | const SkIRect srcRect = SkIRect::MakeWH(src->width(), src->height()); |
350 | |
351 | GrSurfaceProxyView srcView = src->readSurfaceView(); |
352 | if (!srcView.asTextureProxy()) { |
353 | return nullptr; |
354 | } |
355 | |
356 | GrColorType srcColorType = src->colorInfo().colorType(); |
357 | SkAlphaType srcAlphaType = src->colorInfo().alphaType(); |
358 | |
359 | src.reset(); // no longer needed |
360 | |
361 | auto dstRenderTargetContext = GrRenderTargetContext::Make( |
362 | context, srcColorType, std::move(colorSpace), fit, dstSize, 1, GrMipmapped::kNo, |
363 | srcView.proxy()->isProtected(), srcView.origin()); |
364 | if (!dstRenderTargetContext) { |
365 | return nullptr; |
366 | } |
367 | |
368 | GrPaint paint; |
369 | const auto& caps = *context->priv().caps(); |
370 | auto fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(), |
371 | GrSamplerState::Filter::kLinear, SkRect::Make(srcBounds), |
372 | caps); |
373 | paint.setColorFragmentProcessor(std::move(fp)); |
374 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
375 | |
376 | // TODO: using dstII as dstRect results in some image diffs - why? |
377 | SkIRect dstRect(srcRect); |
378 | scale_irect(&dstRect, scaleFactorX, scaleFactorY); |
379 | |
380 | dstRenderTargetContext->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(), |
381 | SkRect::Make(dstRect), SkRect::Make(srcRect)); |
382 | |
383 | return dstRenderTargetContext; |
384 | } |
385 | |
386 | static std::unique_ptr<GrRenderTargetContext> two_pass_gaussian(GrRecordingContext* context, |
387 | GrSurfaceProxyView srcView, |
388 | GrColorType srcColorType, |
389 | SkAlphaType srcAlphaType, |
390 | sk_sp<SkColorSpace> colorSpace, |
391 | SkIRect* srcBounds, |
392 | SkIRect dstBounds, |
393 | float sigmaX, |
394 | float sigmaY, |
395 | int radiusX, |
396 | int radiusY, |
397 | SkTileMode mode, |
398 | SkBackingFit fit) { |
399 | std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext; |
400 | if (sigmaX > 0.0f) { |
401 | SkBackingFit xFit = sigmaY > 0 ? SkBackingFit::kApprox : fit; |
402 | dstRenderTargetContext = convolve_gaussian( |
403 | context, std::move(srcView), srcColorType, srcAlphaType, srcBounds, dstBounds, |
404 | Direction::kX, radiusX, sigmaX, mode, colorSpace, xFit); |
405 | if (!dstRenderTargetContext) { |
406 | return nullptr; |
407 | } |
408 | srcView = dstRenderTargetContext->readSurfaceView(); |
409 | dstBounds = SkIRect::MakeSize(dstBounds.size()); |
410 | } |
411 | |
412 | if (sigmaY == 0.0f) { |
413 | return dstRenderTargetContext; |
414 | } |
415 | |
416 | return convolve_gaussian(context, std::move(srcView), srcColorType, srcAlphaType, srcBounds, |
417 | dstBounds, Direction::kY, radiusY, sigmaY, mode, colorSpace, fit); |
418 | } |
419 | |
420 | namespace SkGpuBlurUtils { |
421 | |
422 | std::unique_ptr<GrRenderTargetContext> LegacyGaussianBlur(GrRecordingContext* context, |
423 | GrSurfaceProxyView srcView, |
424 | GrColorType srcColorType, |
425 | SkAlphaType srcAlphaType, |
426 | sk_sp<SkColorSpace> colorSpace, |
427 | const SkIRect& dstBounds, |
428 | const SkIRect& srcBounds, |
429 | float sigmaX, |
430 | float sigmaY, |
431 | SkTileMode mode, |
432 | SkBackingFit fit) { |
433 | SkASSERT(context); |
434 | TRACE_EVENT2("skia.gpu" , "GaussianBlur" , "sigmaX" , sigmaX, "sigmaY" , sigmaY); |
435 | |
436 | if (!srcView.asTextureProxy()) { |
437 | return nullptr; |
438 | } |
439 | |
440 | int scaleFactorX, radiusX; |
441 | int scaleFactorY, radiusY; |
442 | int maxTextureSize = context->priv().caps()->maxTextureSize(); |
443 | sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX); |
444 | sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY); |
445 | SkASSERT(sigmaX || sigmaY); |
446 | |
447 | auto localSrcBounds = srcBounds; |
448 | |
449 | if (scaleFactorX == 1 && scaleFactorY == 1) { |
450 | // For really small blurs (certainly no wider than 5x5 on desktop GPUs) it is faster to just |
451 | // launch a single non separable kernel vs two launches. |
452 | const int kernelSize = (2 * radiusX + 1) * (2 * radiusY + 1); |
453 | if (sigmaX > 0 && sigmaY > 0 && kernelSize <= GrMatrixConvolutionEffect::kMaxUniformSize) { |
454 | // Apply the proxy offset to src bounds and offset directly |
455 | return convolve_gaussian_2d(context, std::move(srcView), srcColorType, srcBounds, |
456 | dstBounds, radiusX, radiusY, sigmaX, sigmaY, mode, |
457 | colorSpace, fit); |
458 | } |
459 | return two_pass_gaussian(context, std::move(srcView), srcColorType, srcAlphaType, |
460 | std::move(colorSpace), &localSrcBounds, dstBounds, sigmaX, sigmaY, |
461 | radiusX, radiusY, mode, fit); |
462 | } |
463 | |
464 | auto srcOffset = -dstBounds.topLeft(); |
465 | srcView = decimate(context, std::move(srcView), srcColorType, srcAlphaType, srcOffset, |
466 | &localSrcBounds, scaleFactorX, scaleFactorY, mode, colorSpace); |
467 | if (!srcView.proxy()) { |
468 | return nullptr; |
469 | } |
470 | SkASSERT(srcView.asTextureProxy()); |
471 | auto scaledDstBounds = SkIRect::MakeWH(sk_float_ceil(dstBounds.width() / (float)scaleFactorX), |
472 | sk_float_ceil(dstBounds.height() / (float)scaleFactorY)); |
473 | auto rtc = two_pass_gaussian(context, std::move(srcView), srcColorType, srcAlphaType, |
474 | colorSpace, &localSrcBounds, scaledDstBounds, sigmaX, sigmaY, |
475 | radiusX, radiusY, mode, SkBackingFit::kApprox); |
476 | if (!rtc) { |
477 | return nullptr; |
478 | } |
479 | return reexpand(context, std::move(rtc), localSrcBounds, scaleFactorX, scaleFactorY, |
480 | dstBounds.size(), std::move(colorSpace), fit); |
481 | } |
482 | |
483 | } // namespace SkGpuBlurUtils |
484 | |
485 | #endif |
486 | |