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