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
18namespace SkGpuBlurUtils {
19
20#if SK_SUPPORT_GPU
21
22#define MAX_BLUR_SIGMA 4.0f
23
24static 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
31static 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
42static 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
60static 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
115GrTexture* 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