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