• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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