Home | History | Annotate | Download | only in effects
      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/GrTextureDomainEffect.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 
     24 static void scale_rect(SkRect* rect, float xScale, float yScale) {
     25     rect->fLeft   = SkScalarMul(rect->fLeft,   SkFloatToScalar(xScale));
     26     rect->fTop    = SkScalarMul(rect->fTop,    SkFloatToScalar(yScale));
     27     rect->fRight  = SkScalarMul(rect->fRight,  SkFloatToScalar(xScale));
     28     rect->fBottom = SkScalarMul(rect->fBottom, SkFloatToScalar(yScale));
     29 }
     30 
     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     GrAssert(*radius <= GrConvolutionEffect::kMaxKernelRadius);
     39     return sigma;
     40 }
     41 
     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 
     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 
    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     GrAssert(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     GrAssert(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                 GrTextureDomainEffect::kDecal_WrapMode,
    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);
    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);
    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);
    235         clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
    236                                       1, srcIRect.height());
    237         context->clear(&clearRect, 0x0);
    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