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
25using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
26
27static int sigma_radius(float sigma) {
28 SkASSERT(sigma >= 0);
29 return static_cast<int>(ceilf(sigma * 3.0f));
30}
31
32/**
33 * Draws 'rtcRect' into 'renderTargetContext' evaluating a 1D Gaussian over 'srcView'. The src rect
34 * is 'rtcRect' offset by 'rtcToSrcOffset'. 'mode' and 'bounds' are applied to the src coords.
35 */
36static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext,
37 GrSurfaceProxyView srcView,
38 const SkIRect srcSubset,
39 SkIVector rtcToSrcOffset,
40 const SkIRect& rtcRect,
41 SkAlphaType srcAlphaType,
42 Direction direction,
43 int radius,
44 float sigma,
45 SkTileMode mode) {
46 GrPaint paint;
47 auto wm = SkTileModeToWrapMode(mode);
48 auto srcRect = rtcRect.makeOffset(rtcToSrcOffset);
49 std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make(
50 std::move(srcView), srcAlphaType, direction, radius, sigma, wm, srcSubset, &srcRect,
51 *renderTargetContext->caps()));
52 paint.setColorFragmentProcessor(std::move(conv));
53 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
54 renderTargetContext->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
55 SkRect::Make(rtcRect), SkRect::Make(srcRect));
56}
57
58static std::unique_ptr<GrRenderTargetContext> convolve_gaussian_2d(GrRecordingContext* context,
59 GrSurfaceProxyView srcView,
60 GrColorType srcColorType,
61 const SkIRect& srcBounds,
62 const SkIRect& dstBounds,
63 int radiusX,
64 int radiusY,
65 SkScalar sigmaX,
66 SkScalar sigmaY,
67 SkTileMode mode,
68 sk_sp<SkColorSpace> finalCS,
69 SkBackingFit dstFit) {
70 auto renderTargetContext = GrRenderTargetContext::Make(
71 context, srcColorType, std::move(finalCS), dstFit, dstBounds.size(), 1,
72 GrMipmapped::kNo, srcView.proxy()->isProtected(), srcView.origin());
73 if (!renderTargetContext) {
74 return nullptr;
75 }
76
77 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1);
78 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
79 GrPaint paint;
80 auto wm = SkTileModeToWrapMode(mode);
81 auto conv = GrMatrixConvolutionEffect::MakeGaussian(context, std::move(srcView), srcBounds,
82 size, 1.0, 0.0, kernelOffset, wm, true,
83 sigmaX, sigmaY,
84 *renderTargetContext->caps());
85 paint.setColorFragmentProcessor(std::move(conv));
86 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
87
88 // 'dstBounds' is actually in 'srcView' proxy space. It represents the blurred area from src
89 // space that we want to capture in the new RTC at {0, 0}. Hence, we use its size as the rect to
90 // draw and it directly as the local rect.
91 renderTargetContext->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
92 SkRect::Make(dstBounds.size()), SkRect::Make(dstBounds));
93
94 return renderTargetContext;
95}
96
97static std::unique_ptr<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* context,
98 GrSurfaceProxyView srcView,
99 GrColorType srcColorType,
100 SkAlphaType srcAlphaType,
101 SkIRect srcBounds,
102 SkIRect dstBounds,
103 Direction direction,
104 int radius,
105 float sigma,
106 SkTileMode mode,
107 sk_sp<SkColorSpace> finalCS,
108 SkBackingFit fit) {
109 // Logically we're creating an infinite blur of 'srcBounds' of 'srcView' with 'mode' tiling
110 // and then capturing the 'dstBounds' portion in a new RTC where the top left of 'dstBounds' is
111 // at {0, 0} in the new RTC.
112 auto dstRenderTargetContext = GrRenderTargetContext::Make(
113 context, srcColorType, std::move(finalCS), fit, dstBounds.size(), 1, GrMipmapped::kNo,
114 srcView.proxy()->isProtected(), srcView.origin());
115 if (!dstRenderTargetContext) {
116 return nullptr;
117 }
118 // This represents the translation from 'dstRenderTargetContext' coords to 'srcView' coords.
119 auto rtcToSrcOffset = dstBounds.topLeft();
120
121 auto srcBackingBounds = SkIRect::MakeSize(srcView.proxy()->backingStoreDimensions());
122 // We've implemented splitting the dst bounds up into areas that do and do not need to
123 // use shader based tiling but only for some modes...
124 bool canSplit = mode == SkTileMode::kDecal || mode == SkTileMode::kClamp;
125 // ...but it's not worth doing the splitting if we'll get HW tiling instead of shader tiling.
126 bool canHWTile =
127 srcBounds.contains(srcBackingBounds) &&
128 !(mode == SkTileMode::kDecal && !context->priv().caps()->clampToBorderSupport());
129 if (!canSplit || canHWTile) {
130 auto dstRect = SkIRect::MakeSize(dstBounds.size());
131 convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), srcBounds,
132 rtcToSrcOffset, dstRect, srcAlphaType, direction, radius, sigma, mode);
133 return dstRenderTargetContext;
134 }
135
136 // 'left' and 'right' are the sub rects of 'srcBounds' where 'mode' must be enforced.
137 // 'mid' is the area where we can ignore the mode because the kernel does not reach to the
138 // edge of 'srcBounds'.
139 SkIRect mid, left, right;
140 // 'top' and 'bottom' are areas of 'dstBounds' that are entirely above/below 'srcBounds'.
141 // These are areas that we can simply clear in the dst in kDecal mode. If 'srcBounds'
142 // straddles the top edge of 'dstBounds' then 'top' will be inverted and we will skip
143 // processing for the rect. Similar for 'bottom'. The positional/directional labels above refer
144 // to the Direction::kX case and one should think of these as 'left' and 'right' for
145 // Direction::kY.
146 SkIRect top, bottom;
147 if (Direction::kX == direction) {
148 top = {dstBounds.left(), dstBounds.top() , dstBounds.right(), srcBounds.top() };
149 bottom = {dstBounds.left(), srcBounds.bottom(), dstBounds.right(), dstBounds.bottom()};
150
151 // Inset for sub-rect of 'srcBounds' where the x-dir kernel doesn't reach the edges, clipped
152 // vertically to dstBounds.
153 int midA = std::max(srcBounds.top() , dstBounds.top() );
154 int midB = std::min(srcBounds.bottom(), dstBounds.bottom());
155 mid = {srcBounds.left() + radius, midA, srcBounds.right() - radius, midB};
156 if (mid.isEmpty()) {
157 // There is no middle where the bounds can be ignored. Make the left span the whole
158 // width of dst and we will not draw mid or right.
159 left = {dstBounds.left(), mid.top(), dstBounds.right(), mid.bottom()};
160 } else {
161 left = {dstBounds.left(), mid.top(), mid.left() , mid.bottom()};
162 right = {mid.right(), mid.top(), dstBounds.right(), mid.bottom()};
163 }
164 } else {
165 // This is the same as the x direction code if you turn your head 90 degrees CCW. Swap x and
166 // y and swap top/bottom with left/right.
167 top = {dstBounds.left(), dstBounds.top(), srcBounds.left() , dstBounds.bottom()};
168 bottom = {srcBounds.right(), dstBounds.top(), dstBounds.right(), dstBounds.bottom()};
169
170 int midA = std::max(srcBounds.left() , dstBounds.left() );
171 int midB = std::min(srcBounds.right(), dstBounds.right());
172 mid = {midA, srcBounds.top() + radius, midB, srcBounds.bottom() - radius};
173
174 if (mid.isEmpty()) {
175 left = {mid.left(), dstBounds.top(), mid.right(), dstBounds.bottom()};
176 } else {
177 left = {mid.left(), dstBounds.top(), mid.right(), mid.top() };
178 right = {mid.left(), mid.bottom() , mid.right(), dstBounds.bottom()};
179 }
180 }
181
182 auto convolve = [&](SkIRect rect) {
183 // Transform rect into the render target's coord system.
184 rect.offset(-rtcToSrcOffset);
185 convolve_gaussian_1d(dstRenderTargetContext.get(), srcView, srcBounds, rtcToSrcOffset, rect,
186 srcAlphaType, direction, radius, sigma, mode);
187 };
188 auto clear = [&](SkIRect rect) {
189 // Transform rect into the render target's coord system.
190 rect.offset(-rtcToSrcOffset);
191 dstRenderTargetContext->priv().clearAtLeast(rect, SK_PMColor4fTRANSPARENT);
192 };
193
194 // Doing mid separately will cause two draws to occur (left and right batch together). At
195 // small sizes of mid it is worse to issue more draws than to just execute the slightly
196 // more complicated shader that implements the tile mode across mid. This threshold is
197 // very arbitrary right now. It is believed that a 21x44 mid on a Moto G4 is a significant
198 // regression compared to doing one draw but it has not been locally evaluated or tuned.
199 // The optimal cutoff is likely to vary by GPU.
200 if (!mid.isEmpty() && mid.width()*mid.height() < 256*256) {
201 left.join(mid);
202 left.join(right);
203 mid = SkIRect::MakeEmpty();
204 right = SkIRect::MakeEmpty();
205 // It's unknown whether for kDecal it'd be better to expand the draw rather than a draw and
206 // up to two clears.
207 if (mode == SkTileMode::kClamp) {
208 left.join(top);
209 left.join(bottom);
210 top = SkIRect::MakeEmpty();
211 bottom = SkIRect::MakeEmpty();
212 }
213 }
214
215 if (!top.isEmpty()) {
216 if (mode == SkTileMode::kDecal) {
217 clear(top);
218 } else {
219 convolve(top);
220 }
221 }
222
223 if (!bottom.isEmpty()) {
224 if (mode == SkTileMode::kDecal) {
225 clear(bottom);
226 } else {
227 convolve(bottom);
228 }
229 }
230
231 if (mid.isEmpty()) {
232 convolve(left);
233 } else {
234 convolve(left);
235 convolve(right);
236 convolve(mid);
237 }
238 return dstRenderTargetContext;
239}
240
241// Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'. At this point, we are
242// expanding an intermediate image, so there's no need to account for a proxy offset from the
243// original input.
244static std::unique_ptr<GrRenderTargetContext> reexpand(GrRecordingContext* context,
245 std::unique_ptr<GrRenderTargetContext> src,
246 const SkRect& srcBounds,
247 SkISize dstSize,
248 sk_sp<SkColorSpace> colorSpace,
249 SkBackingFit fit) {
250 GrSurfaceProxyView srcView = src->readSurfaceView();
251 if (!srcView.asTextureProxy()) {
252 return nullptr;
253 }
254
255 GrColorType srcColorType = src->colorInfo().colorType();
256 SkAlphaType srcAlphaType = src->colorInfo().alphaType();
257
258 src.reset(); // no longer needed
259
260 auto dstRenderTargetContext = GrRenderTargetContext::Make(
261 context, srcColorType, std::move(colorSpace), fit, dstSize, 1, GrMipmapped::kNo,
262 srcView.proxy()->isProtected(), srcView.origin());
263 if (!dstRenderTargetContext) {
264 return nullptr;
265 }
266
267 GrPaint paint;
268 auto fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(),
269 GrSamplerState::Filter::kLinear, srcBounds, srcBounds,
270 *context->priv().caps());
271 paint.setColorFragmentProcessor(std::move(fp));
272 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
273
274 dstRenderTargetContext->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
275 SkRect::Make(dstSize), srcBounds);
276
277 return dstRenderTargetContext;
278}
279
280static std::unique_ptr<GrRenderTargetContext> two_pass_gaussian(GrRecordingContext* context,
281 GrSurfaceProxyView srcView,
282 GrColorType srcColorType,
283 SkAlphaType srcAlphaType,
284 sk_sp<SkColorSpace> colorSpace,
285 SkIRect srcBounds,
286 SkIRect dstBounds,
287 float sigmaX,
288 float sigmaY,
289 int radiusX,
290 int radiusY,
291 SkTileMode mode,
292 SkBackingFit fit) {
293 SkASSERT(sigmaX || sigmaY);
294 std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext;
295 if (sigmaX > 0.0f) {
296 SkBackingFit xFit = sigmaY > 0 ? SkBackingFit::kApprox : fit;
297 // Expand the dstBounds vertically to produce necessary content for the y-pass. Then we will
298 // clip these in a tile-mode dependent way to ensure the tile-mode gets implemented
299 // correctly. However, if we're not going to do a y-pass then we must use the original
300 // dstBounds without clipping to produce the correct output size.
301 SkIRect xPassDstBounds = dstBounds;
302 if (sigmaY) {
303 xPassDstBounds.outset(0, radiusY);
304 if (mode == SkTileMode::kRepeat || mode == SkTileMode::kMirror) {
305 int srcH = srcBounds.height();
306 int srcTop = srcBounds.top();
307 if (mode == SkTileMode::kMirror) {
308 srcTop -= srcH;
309 srcH *= 2;
310 }
311
312 float floatH = srcH;
313 // First row above the dst rect where we should restart the tile mode.
314 int n = sk_float_floor2int_no_saturate((xPassDstBounds.top() - srcTop)/floatH);
315 int topClip = srcTop + n*srcH;
316
317 // First row above below the dst rect where we should restart the tile mode.
318 n = sk_float_ceil2int_no_saturate(
319 (xPassDstBounds.bottom() - srcBounds.bottom())/floatH);
320 int bottomClip = srcBounds.bottom() + n*srcH;
321
322 xPassDstBounds.fTop = std::max(xPassDstBounds.top(), topClip);
323 xPassDstBounds.fBottom = std::min(xPassDstBounds.bottom(), bottomClip);
324 } else {
325 if (xPassDstBounds.fBottom <= srcBounds.top()) {
326 if (mode == SkTileMode::kDecal) {
327 return nullptr;
328 }
329 xPassDstBounds.fTop = srcBounds.top();
330 xPassDstBounds.fBottom = xPassDstBounds.fTop + 1;
331 } else if (xPassDstBounds.fTop >= srcBounds.bottom()) {
332 if (mode == SkTileMode::kDecal) {
333 return nullptr;
334 }
335 xPassDstBounds.fBottom = srcBounds.bottom();
336 xPassDstBounds.fTop = xPassDstBounds.fBottom - 1;
337 } else {
338 xPassDstBounds.fTop = std::max(xPassDstBounds.fTop, srcBounds.top());
339 xPassDstBounds.fBottom = std::min(xPassDstBounds.fBottom, srcBounds.bottom());
340 }
341 int leftSrcEdge = srcBounds.fLeft - radiusX ;
342 int rightSrcEdge = srcBounds.fRight + radiusX;
343 if (mode == SkTileMode::kClamp) {
344 // In clamp the column just outside the src bounds has the same value as the
345 // column just inside, unlike decal.
346 leftSrcEdge += 1;
347 rightSrcEdge -= 1;
348 }
349 if (xPassDstBounds.fRight <= leftSrcEdge) {
350 if (mode == SkTileMode::kDecal) {
351 return nullptr;
352 }
353 xPassDstBounds.fLeft = xPassDstBounds.fRight - 1;
354 } else {
355 xPassDstBounds.fLeft = std::max(xPassDstBounds.fLeft, leftSrcEdge);
356 }
357 if (xPassDstBounds.fLeft >= rightSrcEdge) {
358 if (mode == SkTileMode::kDecal) {
359 return nullptr;
360 }
361 xPassDstBounds.fRight = xPassDstBounds.fLeft + 1;
362 } else {
363 xPassDstBounds.fRight = std::min(xPassDstBounds.fRight, rightSrcEdge);
364 }
365 }
366 }
367 dstRenderTargetContext = convolve_gaussian(
368 context, std::move(srcView), srcColorType, srcAlphaType, srcBounds, xPassDstBounds,
369 Direction::kX, radiusX, sigmaX, mode, colorSpace, xFit);
370 if (!dstRenderTargetContext) {
371 return nullptr;
372 }
373 srcView = dstRenderTargetContext->readSurfaceView();
374 SkIVector newDstBoundsOffset = dstBounds.topLeft() - xPassDstBounds.topLeft();
375 dstBounds = SkIRect::MakeSize(dstBounds.size()).makeOffset(newDstBoundsOffset);
376 srcBounds = SkIRect::MakeSize(xPassDstBounds.size());
377 }
378
379 if (sigmaY == 0.0f) {
380 return dstRenderTargetContext;
381 }
382
383 return convolve_gaussian(context, std::move(srcView), srcColorType, srcAlphaType, srcBounds,
384 dstBounds, Direction::kY, radiusY, sigmaY, mode, colorSpace, fit);
385}
386
387namespace SkGpuBlurUtils {
388
389std::unique_ptr<GrRenderTargetContext> LegacyGaussianBlur(GrRecordingContext* context,
390 GrSurfaceProxyView srcView,
391 GrColorType srcColorType,
392 SkAlphaType srcAlphaType,
393 sk_sp<SkColorSpace> colorSpace,
394 const SkIRect& dstBounds,
395 const SkIRect& srcBounds,
396 float sigmaX,
397 float sigmaY,
398 SkTileMode mode,
399 SkBackingFit fit);
400
401std::unique_ptr<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
402 GrSurfaceProxyView srcView,
403 GrColorType srcColorType,
404 SkAlphaType srcAlphaType,
405 sk_sp<SkColorSpace> colorSpace,
406 SkIRect dstBounds,
407 SkIRect srcBounds,
408 float sigmaX,
409 float sigmaY,
410 SkTileMode mode,
411 SkBackingFit fit) {
412#ifdef SK_USE_LEGACY_GPU_BLUR
413 return LegacyGaussianBlur(context, srcView, srcColorType, srcAlphaType, std::move(colorSpace),
414 dstBounds, srcBounds, sigmaX, sigmaY, mode, fit);
415#endif
416 SkASSERT(context);
417 TRACE_EVENT2("skia.gpu", "GaussianBlur", "sigmaX", sigmaX, "sigmaY", sigmaY);
418
419 if (!srcView.asTextureProxy()) {
420 return nullptr;
421 }
422
423 int maxRenderTargetSize = context->priv().caps()->maxRenderTargetSize();
424 if (dstBounds.width() > maxRenderTargetSize || dstBounds.height() > maxRenderTargetSize) {
425 return nullptr;
426 }
427
428 // Attempt to reduce the srcBounds in order to detect that we can set the sigmas to zero or
429 // to reduce the amount of work to rescale the source if sigmas are large. TODO: Could consider
430 // how to minimize the required source bounds for repeat/mirror modes.
431 if (mode == SkTileMode::kClamp || mode == SkTileMode::kDecal) {
432 int radiusX = sigma_radius(sigmaX);
433 int radiusY = sigma_radius(sigmaY);
434 SkIRect reach = dstBounds.makeOutset(radiusX, radiusY);
435 SkIRect intersection;
436 if (!intersection.intersect(reach, srcBounds)) {
437 if (mode == SkTileMode::kDecal) {
438 return nullptr;
439 } else {
440 if (reach.fLeft >= srcBounds.fRight) {
441 srcBounds.fLeft = srcBounds.fRight - 1;
442 } else if (reach.fRight <= srcBounds.fLeft) {
443 srcBounds.fRight = srcBounds.fLeft + 1;
444 }
445 if (reach.fTop >= srcBounds.fBottom) {
446 srcBounds.fTop = srcBounds.fBottom - 1;
447 } else if (reach.fBottom <= srcBounds.fTop) {
448 srcBounds.fBottom = srcBounds.fTop + 1;
449 }
450 }
451 } else {
452 srcBounds = intersection;
453 }
454 }
455
456 if (mode != SkTileMode::kDecal) {
457 // All non-decal tile modes are equivalent for one pixel width/height src and amount to a
458 // single color value repeated at each column/row. Applying the normalized kernel to that
459 // column/row yields that same color. So no blurring is necessary.
460 if (srcBounds.width() == 1) {
461 sigmaX = 0.f;
462 }
463 if (srcBounds.height() == 1) {
464 sigmaY = 0.f;
465 }
466 }
467
468 // If we determined that there is no blurring necessary in either direction then just do a
469 // a draw that applies the tile mode.
470 if (!sigmaX && !sigmaY) {
471 auto result = GrRenderTargetContext::Make(context, srcColorType, std::move(colorSpace), fit,
472 dstBounds.size());
473 GrSamplerState sampler(SkTileModeToWrapMode(mode), GrSamplerState::Filter::kNearest);
474 auto fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(),
475 sampler, SkRect::Make(srcBounds),
476 SkRect::Make(dstBounds), *context->priv().caps());
477 GrPaint paint;
478 paint.setColorFragmentProcessor(std::move(fp));
479 result->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
480 SkRect::Make(dstBounds.size()));
481 return result;
482 }
483
484 if (sigmaX <= MAX_BLUR_SIGMA && sigmaY <= MAX_BLUR_SIGMA) {
485 int radiusX = sigma_radius(sigmaX);
486 int radiusY = sigma_radius(sigmaY);
487 SkASSERT(radiusX <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius);
488 SkASSERT(radiusY <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius);
489 // For really small blurs (certainly no wider than 5x5 on desktop GPUs) it is faster to just
490 // launch a single non separable kernel vs two launches.
491 const int kernelSize = (2 * radiusX + 1) * (2 * radiusY + 1);
492 if (sigmaX > 0 && sigmaY > 0 && kernelSize <= GrMatrixConvolutionEffect::kMaxUniformSize) {
493 // Apply the proxy offset to src bounds and offset directly
494 return convolve_gaussian_2d(context, std::move(srcView), srcColorType, srcBounds,
495 dstBounds, radiusX, radiusY, sigmaX, sigmaY, mode,
496 std::move(colorSpace), fit);
497 }
498 return two_pass_gaussian(context, std::move(srcView), srcColorType, srcAlphaType,
499 std::move(colorSpace), srcBounds, dstBounds, sigmaX, sigmaY,
500 radiusX, radiusY, mode, fit);
501 }
502
503 float scaleX = sigmaX > MAX_BLUR_SIGMA ? MAX_BLUR_SIGMA/sigmaX : 1.f;
504 float scaleY = sigmaY > MAX_BLUR_SIGMA ? MAX_BLUR_SIGMA/sigmaY : 1.f;
505 // We round down here so that when we recalculate sigmas we know they will be below
506 // MAX_BLUR_SIGMA.
507 SkISize rescaledSize = {sk_float_floor2int(srcBounds.width() *scaleX),
508 sk_float_floor2int(srcBounds.height()*scaleY)};
509 if (rescaledSize.isEmpty()) {
510 // TODO: Handle this degenerate case.
511 return nullptr;
512 }
513 // Compute the sigmas using the actual scale factors used once we integerized the rescaledSize.
514 scaleX = static_cast<float>(rescaledSize.width()) /srcBounds.width();
515 scaleY = static_cast<float>(rescaledSize.height())/srcBounds.height();
516 sigmaX *= scaleX;
517 sigmaY *= scaleY;
518
519 auto srcCtx = GrSurfaceContext::Make(context, srcView, srcColorType, srcAlphaType, colorSpace);
520 SkASSERT(srcCtx);
521 GrImageInfo rescaledII(srcColorType, srcAlphaType, colorSpace, rescaledSize);
522 srcCtx = srcCtx->rescale(rescaledII, srcCtx->origin(), srcBounds, SkSurface::RescaleGamma::kSrc,
523 kLow_SkFilterQuality);
524 if (!srcCtx) {
525 return nullptr;
526 }
527 srcView = srcCtx->readSurfaceView();
528 // Drop the context so we don't hold the proxy longer than necessary.
529 srcCtx.reset();
530
531 // Compute the dst bounds in the scaled down space. First move the origin to be at the top
532 // left since we trimmed off everything above and to the left of the original src bounds during
533 // the rescale.
534 SkRect scaledDstBounds = SkRect::Make(dstBounds.makeOffset(-srcBounds.topLeft()));
535 scaledDstBounds.fLeft *= scaleX;
536 scaledDstBounds.fTop *= scaleY;
537 scaledDstBounds.fRight *= scaleX;
538 scaledDstBounds.fBottom *= scaleY;
539 // Turn the scaled down dst bounds into an integer pixel rect.
540 auto scaledDstBoundsI = scaledDstBounds.roundOut();
541
542 auto rtc = GaussianBlur(context, std::move(srcView), srcColorType, srcAlphaType, colorSpace,
543 scaledDstBoundsI, SkIRect::MakeSize(rescaledSize), sigmaX, sigmaY, mode,
544 fit);
545 if (!rtc) {
546 return nullptr;
547 }
548 // We rounded out the integer scaled dst bounds. Select the fractional dst bounds from the
549 // integer dimension blurred result when we scale back up.
550 scaledDstBounds.offset(-scaledDstBoundsI.left(), -scaledDstBoundsI.top());
551 return reexpand(context, std::move(rtc), scaledDstBounds, dstBounds.size(),
552 std::move(colorSpace), fit);
553}
554} // namespace SkGpuBlurUtils
555
556#endif
557