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