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 "SkGpuBlurUtils.h"
9
10 #include "SkRect.h"
11
12 #if SK_SUPPORT_GPU
13 #include "effects/GrConvolutionEffect.h"
14 #include "effects/GrTextureDomain.h"
15 #include "GrContext.h"
16 #endif
17
18 namespace SkGpuBlurUtils {
19
20 #if SK_SUPPORT_GPU
21
22 #define MAX_BLUR_SIGMA 4.0f
23
scale_rect(SkRect * rect,float xScale,float yScale)24 static void scale_rect(SkRect* rect, float xScale, float yScale) {
25 rect->fLeft = SkScalarMul(rect->fLeft, xScale);
26 rect->fTop = SkScalarMul(rect->fTop, yScale);
27 rect->fRight = SkScalarMul(rect->fRight, xScale);
28 rect->fBottom = SkScalarMul(rect->fBottom, yScale);
29 }
30
adjust_sigma(float sigma,int maxTextureSize,int * scaleFactor,int * radius)31 static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) {
32 *scaleFactor = 1;
33 while (sigma > MAX_BLUR_SIGMA) {
34 *scaleFactor *= 2;
35 sigma *= 0.5f;
36 if (*scaleFactor > maxTextureSize) {
37 *scaleFactor = maxTextureSize;
38 sigma = MAX_BLUR_SIGMA;
39 }
40 }
41 *radius = static_cast<int>(ceilf(sigma * 3.0f));
42 SkASSERT(*radius <= GrConvolutionEffect::kMaxKernelRadius);
43 return sigma;
44 }
45
convolve_gaussian_pass(GrContext * context,const SkRect & srcRect,const SkRect & dstRect,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,bool useBounds,float bounds[2])46 static void convolve_gaussian_pass(GrContext* context,
47 const SkRect& srcRect,
48 const SkRect& dstRect,
49 GrTexture* texture,
50 Gr1DKernelEffect::Direction direction,
51 int radius,
52 float sigma,
53 bool useBounds,
54 float bounds[2]) {
55 GrPaint paint;
56 paint.reset();
57 SkAutoTUnref<GrEffectRef> conv(GrConvolutionEffect::CreateGaussian(
58 texture, direction, radius, sigma, useBounds, bounds));
59 paint.reset();
60 paint.addColorEffect(conv);
61 context->drawRectToRect(paint, dstRect, srcRect);
62 }
63
convolve_gaussian(GrContext * context,const SkRect & srcRect,const SkRect & dstRect,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,bool cropToSrcRect)64 static void convolve_gaussian(GrContext* context,
65 const SkRect& srcRect,
66 const SkRect& dstRect,
67 GrTexture* texture,
68 Gr1DKernelEffect::Direction direction,
69 int radius,
70 float sigma,
71 bool cropToSrcRect) {
72 float bounds[2] = { 0.0f, 1.0f };
73 if (!cropToSrcRect) {
74 convolve_gaussian_pass(context, srcRect, dstRect, texture,
75 direction, radius, sigma, false, bounds);
76 return;
77 }
78 SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect;
79 SkRect middleSrcRect = srcRect, middleDstRect = dstRect;
80 SkRect upperSrcRect = srcRect, upperDstRect = dstRect;
81 SkScalar size;
82 SkScalar rad = SkIntToScalar(radius);
83 if (direction == Gr1DKernelEffect::kX_Direction) {
84 bounds[0] = SkScalarToFloat(srcRect.left()) / texture->width();
85 bounds[1] = SkScalarToFloat(srcRect.right()) / texture->width();
86 size = srcRect.width();
87 lowerSrcRect.fRight = srcRect.left() + rad;
88 lowerDstRect.fRight = dstRect.left() + rad;
89 upperSrcRect.fLeft = srcRect.right() - rad;
90 upperDstRect.fLeft = dstRect.right() - rad;
91 middleSrcRect.inset(rad, 0);
92 middleDstRect.inset(rad, 0);
93 } else {
94 bounds[0] = SkScalarToFloat(srcRect.top()) / texture->height();
95 bounds[1] = SkScalarToFloat(srcRect.bottom()) / texture->height();
96 size = srcRect.height();
97 lowerSrcRect.fBottom = srcRect.top() + rad;
98 lowerDstRect.fBottom = dstRect.top() + rad;
99 upperSrcRect.fTop = srcRect.bottom() - rad;
100 upperDstRect.fTop = dstRect.bottom() - rad;
101 middleSrcRect.inset(0, rad);
102 middleDstRect.inset(0, rad);
103 }
104 if (radius >= size * SK_ScalarHalf) {
105 // Blur radius covers srcRect; use bounds over entire draw
106 convolve_gaussian_pass(context, srcRect, dstRect, texture,
107 direction, radius, sigma, true, bounds);
108 } else {
109 // Draw upper and lower margins with bounds; middle without.
110 convolve_gaussian_pass(context, lowerSrcRect, lowerDstRect, texture,
111 direction, radius, sigma, true, bounds);
112 convolve_gaussian_pass(context, upperSrcRect, upperDstRect, texture,
113 direction, radius, sigma, true, bounds);
114 convolve_gaussian_pass(context, middleSrcRect, middleDstRect, texture,
115 direction, radius, sigma, false, bounds);
116 }
117 }
118
GaussianBlur(GrContext * context,GrTexture * srcTexture,bool canClobberSrc,const SkRect & rect,bool cropToRect,float sigmaX,float sigmaY)119 GrTexture* GaussianBlur(GrContext* context,
120 GrTexture* srcTexture,
121 bool canClobberSrc,
122 const SkRect& rect,
123 bool cropToRect,
124 float sigmaX,
125 float sigmaY) {
126 SkASSERT(NULL != context);
127
128 GrContext::AutoRenderTarget art(context);
129
130 GrContext::AutoMatrix am;
131 am.setIdentity(context);
132
133 SkIRect clearRect;
134 int scaleFactorX, radiusX;
135 int scaleFactorY, radiusY;
136 int maxTextureSize = context->getMaxTextureSize();
137 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
138 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
139
140 SkRect srcRect(rect);
141 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
142 srcRect.roundOut();
143 scale_rect(&srcRect, static_cast<float>(scaleFactorX),
144 static_cast<float>(scaleFactorY));
145
146 GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height()));
147
148 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
149 kRGBA_8888_GrPixelConfig == srcTexture->config() ||
150 kAlpha_8_GrPixelConfig == srcTexture->config());
151
152 GrTextureDesc desc;
153 desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
154 desc.fWidth = SkScalarFloorToInt(srcRect.width());
155 desc.fHeight = SkScalarFloorToInt(srcRect.height());
156 desc.fConfig = srcTexture->config();
157
158 GrAutoScratchTexture temp1, temp2;
159 GrTexture* dstTexture = temp1.set(context, desc);
160 GrTexture* tempTexture = canClobberSrc ? srcTexture : temp2.set(context, desc);
161 if (NULL == dstTexture || NULL == tempTexture) {
162 return NULL;
163 }
164
165 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
166 GrPaint paint;
167 SkMatrix matrix;
168 matrix.setIDiv(srcTexture->width(), srcTexture->height());
169 context->setRenderTarget(dstTexture->asRenderTarget());
170 SkRect dstRect(srcRect);
171 if (cropToRect && i == 1) {
172 dstRect.offset(-dstRect.fLeft, -dstRect.fTop);
173 SkRect domain;
174 matrix.mapRect(&domain, rect);
175 domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f,
176 i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f);
177 SkAutoTUnref<GrEffectRef> effect(GrTextureDomainEffect::Create(
178 srcTexture,
179 matrix,
180 domain,
181 GrTextureDomain::kDecal_Mode,
182 GrTextureParams::kBilerp_FilterMode));
183 paint.addColorEffect(effect);
184 } else {
185 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
186 paint.addColorTextureEffect(srcTexture, matrix, params);
187 }
188 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
189 i < scaleFactorY ? 0.5f : 1.0f);
190 context->drawRectToRect(paint, dstRect, srcRect);
191 srcRect = dstRect;
192 srcTexture = dstTexture;
193 SkTSwap(dstTexture, tempTexture);
194 }
195
196 SkIRect srcIRect;
197 srcRect.roundOut(&srcIRect);
198
199 if (sigmaX > 0.0f) {
200 if (scaleFactorX > 1) {
201 // Clear out a radius to the right of the srcRect to prevent the
202 // X convolution from reading garbage.
203 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
204 radiusX, srcIRect.height());
205 context->clear(&clearRect, 0x0, false);
206 }
207 context->setRenderTarget(dstTexture->asRenderTarget());
208 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
209 convolve_gaussian(context, srcRect, dstRect, srcTexture,
210 Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect);
211 srcTexture = dstTexture;
212 srcRect = dstRect;
213 SkTSwap(dstTexture, tempTexture);
214 }
215
216 if (sigmaY > 0.0f) {
217 if (scaleFactorY > 1 || sigmaX > 0.0f) {
218 // Clear out a radius below the srcRect to prevent the Y
219 // convolution from reading garbage.
220 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
221 srcIRect.width(), radiusY);
222 context->clear(&clearRect, 0x0, false);
223 }
224
225 context->setRenderTarget(dstTexture->asRenderTarget());
226 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
227 convolve_gaussian(context, srcRect, dstRect, srcTexture,
228 Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect);
229 srcTexture = dstTexture;
230 srcRect = dstRect;
231 SkTSwap(dstTexture, tempTexture);
232 }
233
234 if (scaleFactorX > 1 || scaleFactorY > 1) {
235 // Clear one pixel to the right and below, to accommodate bilinear
236 // upsampling.
237 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
238 srcIRect.width() + 1, 1);
239 context->clear(&clearRect, 0x0, false);
240 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
241 1, srcIRect.height());
242 context->clear(&clearRect, 0x0, false);
243 SkMatrix matrix;
244 matrix.setIDiv(srcTexture->width(), srcTexture->height());
245 context->setRenderTarget(dstTexture->asRenderTarget());
246
247 GrPaint paint;
248 // FIXME: this should be mitchell, not bilinear.
249 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
250 paint.addColorTextureEffect(srcTexture, matrix, params);
251
252 SkRect dstRect(srcRect);
253 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
254 context->drawRectToRect(paint, dstRect, srcRect);
255 srcRect = dstRect;
256 srcTexture = dstTexture;
257 SkTSwap(dstTexture, tempTexture);
258 }
259 if (srcTexture == temp1.texture()) {
260 return temp1.detach();
261 } else if (srcTexture == temp2.texture()) {
262 return temp2.detach();
263 } else {
264 srcTexture->ref();
265 return srcTexture;
266 }
267 }
268 #endif
269
270 }
271