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/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 
     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 
     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 
     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 
     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 
    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