michael@0: /* michael@0: * Copyright 2013 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "SkGpuBlurUtils.h" michael@0: michael@0: #include "SkRect.h" michael@0: michael@0: #if SK_SUPPORT_GPU michael@0: #include "effects/GrConvolutionEffect.h" michael@0: #include "effects/GrTextureDomain.h" michael@0: #include "GrContext.h" michael@0: #endif michael@0: michael@0: namespace SkGpuBlurUtils { michael@0: michael@0: #if SK_SUPPORT_GPU michael@0: michael@0: #define MAX_BLUR_SIGMA 4.0f michael@0: michael@0: static void scale_rect(SkRect* rect, float xScale, float yScale) { michael@0: rect->fLeft = SkScalarMul(rect->fLeft, xScale); michael@0: rect->fTop = SkScalarMul(rect->fTop, yScale); michael@0: rect->fRight = SkScalarMul(rect->fRight, xScale); michael@0: rect->fBottom = SkScalarMul(rect->fBottom, yScale); michael@0: } michael@0: michael@0: static float adjust_sigma(float sigma, int *scaleFactor, int *radius) { michael@0: *scaleFactor = 1; michael@0: while (sigma > MAX_BLUR_SIGMA) { michael@0: *scaleFactor *= 2; michael@0: sigma *= 0.5f; michael@0: } michael@0: *radius = static_cast(ceilf(sigma * 3.0f)); michael@0: SkASSERT(*radius <= GrConvolutionEffect::kMaxKernelRadius); michael@0: return sigma; michael@0: } michael@0: michael@0: static void convolve_gaussian_pass(GrContext* context, michael@0: const SkRect& srcRect, michael@0: const SkRect& dstRect, michael@0: GrTexture* texture, michael@0: Gr1DKernelEffect::Direction direction, michael@0: int radius, michael@0: float sigma, michael@0: bool useBounds, michael@0: float bounds[2]) { michael@0: GrPaint paint; michael@0: paint.reset(); michael@0: SkAutoTUnref conv(GrConvolutionEffect::CreateGaussian( michael@0: texture, direction, radius, sigma, useBounds, bounds)); michael@0: paint.reset(); michael@0: paint.addColorEffect(conv); michael@0: context->drawRectToRect(paint, dstRect, srcRect); michael@0: } michael@0: michael@0: static void convolve_gaussian(GrContext* context, michael@0: const SkRect& srcRect, michael@0: const SkRect& dstRect, michael@0: GrTexture* texture, michael@0: Gr1DKernelEffect::Direction direction, michael@0: int radius, michael@0: float sigma, michael@0: bool cropToSrcRect) { michael@0: float bounds[2] = { 0.0f, 1.0f }; michael@0: if (!cropToSrcRect) { michael@0: convolve_gaussian_pass(context, srcRect, dstRect, texture, michael@0: direction, radius, sigma, false, bounds); michael@0: return; michael@0: } michael@0: SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect; michael@0: SkRect middleSrcRect = srcRect, middleDstRect = dstRect; michael@0: SkRect upperSrcRect = srcRect, upperDstRect = dstRect; michael@0: SkScalar size; michael@0: SkScalar rad = SkIntToScalar(radius); michael@0: if (direction == Gr1DKernelEffect::kX_Direction) { michael@0: bounds[0] = SkScalarToFloat(srcRect.left()) / texture->width(); michael@0: bounds[1] = SkScalarToFloat(srcRect.right()) / texture->width(); michael@0: size = srcRect.width(); michael@0: lowerSrcRect.fRight = srcRect.left() + rad; michael@0: lowerDstRect.fRight = dstRect.left() + rad; michael@0: upperSrcRect.fLeft = srcRect.right() - rad; michael@0: upperDstRect.fLeft = dstRect.right() - rad; michael@0: middleSrcRect.inset(rad, 0); michael@0: middleDstRect.inset(rad, 0); michael@0: } else { michael@0: bounds[0] = SkScalarToFloat(srcRect.top()) / texture->height(); michael@0: bounds[1] = SkScalarToFloat(srcRect.bottom()) / texture->height(); michael@0: size = srcRect.height(); michael@0: lowerSrcRect.fBottom = srcRect.top() + rad; michael@0: lowerDstRect.fBottom = dstRect.top() + rad; michael@0: upperSrcRect.fTop = srcRect.bottom() - rad; michael@0: upperDstRect.fTop = dstRect.bottom() - rad; michael@0: middleSrcRect.inset(0, rad); michael@0: middleDstRect.inset(0, rad); michael@0: } michael@0: if (radius >= size * SK_ScalarHalf) { michael@0: // Blur radius covers srcRect; use bounds over entire draw michael@0: convolve_gaussian_pass(context, srcRect, dstRect, texture, michael@0: direction, radius, sigma, true, bounds); michael@0: } else { michael@0: // Draw upper and lower margins with bounds; middle without. michael@0: convolve_gaussian_pass(context, lowerSrcRect, lowerDstRect, texture, michael@0: direction, radius, sigma, true, bounds); michael@0: convolve_gaussian_pass(context, upperSrcRect, upperDstRect, texture, michael@0: direction, radius, sigma, true, bounds); michael@0: convolve_gaussian_pass(context, middleSrcRect, middleDstRect, texture, michael@0: direction, radius, sigma, false, bounds); michael@0: } michael@0: } michael@0: michael@0: GrTexture* GaussianBlur(GrContext* context, michael@0: GrTexture* srcTexture, michael@0: bool canClobberSrc, michael@0: const SkRect& rect, michael@0: bool cropToRect, michael@0: float sigmaX, michael@0: float sigmaY) { michael@0: SkASSERT(NULL != context); michael@0: michael@0: GrContext::AutoRenderTarget art(context); michael@0: michael@0: GrContext::AutoMatrix am; michael@0: am.setIdentity(context); michael@0: michael@0: SkIRect clearRect; michael@0: int scaleFactorX, radiusX; michael@0: int scaleFactorY, radiusY; michael@0: sigmaX = adjust_sigma(sigmaX, &scaleFactorX, &radiusX); michael@0: sigmaY = adjust_sigma(sigmaY, &scaleFactorY, &radiusY); michael@0: michael@0: SkRect srcRect(rect); michael@0: scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); michael@0: srcRect.roundOut(); michael@0: scale_rect(&srcRect, static_cast(scaleFactorX), michael@0: static_cast(scaleFactorY)); michael@0: michael@0: GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height())); michael@0: michael@0: SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() || michael@0: kRGBA_8888_GrPixelConfig == srcTexture->config() || michael@0: kAlpha_8_GrPixelConfig == srcTexture->config()); michael@0: michael@0: GrTextureDesc desc; michael@0: desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit; michael@0: desc.fWidth = SkScalarFloorToInt(srcRect.width()); michael@0: desc.fHeight = SkScalarFloorToInt(srcRect.height()); michael@0: desc.fConfig = srcTexture->config(); michael@0: michael@0: GrAutoScratchTexture temp1, temp2; michael@0: GrTexture* dstTexture = temp1.set(context, desc); michael@0: GrTexture* tempTexture = canClobberSrc ? srcTexture : temp2.set(context, desc); michael@0: if (NULL == dstTexture || NULL == tempTexture) { michael@0: return NULL; michael@0: } michael@0: michael@0: for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { michael@0: GrPaint paint; michael@0: SkMatrix matrix; michael@0: matrix.setIDiv(srcTexture->width(), srcTexture->height()); michael@0: context->setRenderTarget(dstTexture->asRenderTarget()); michael@0: SkRect dstRect(srcRect); michael@0: if (cropToRect && i == 1) { michael@0: dstRect.offset(-dstRect.fLeft, -dstRect.fTop); michael@0: SkRect domain; michael@0: matrix.mapRect(&domain, rect); michael@0: domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f, michael@0: i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f); michael@0: SkAutoTUnref effect(GrTextureDomainEffect::Create( michael@0: srcTexture, michael@0: matrix, michael@0: domain, michael@0: GrTextureDomain::kDecal_Mode, michael@0: GrTextureParams::kBilerp_FilterMode)); michael@0: paint.addColorEffect(effect); michael@0: } else { michael@0: GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); michael@0: paint.addColorTextureEffect(srcTexture, matrix, params); michael@0: } michael@0: scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f, michael@0: i < scaleFactorY ? 0.5f : 1.0f); michael@0: context->drawRectToRect(paint, dstRect, srcRect); michael@0: srcRect = dstRect; michael@0: srcTexture = dstTexture; michael@0: SkTSwap(dstTexture, tempTexture); michael@0: } michael@0: michael@0: SkIRect srcIRect; michael@0: srcRect.roundOut(&srcIRect); michael@0: michael@0: if (sigmaX > 0.0f) { michael@0: if (scaleFactorX > 1) { michael@0: // Clear out a radius to the right of the srcRect to prevent the michael@0: // X convolution from reading garbage. michael@0: clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, michael@0: radiusX, srcIRect.height()); michael@0: context->clear(&clearRect, 0x0, false); michael@0: } michael@0: context->setRenderTarget(dstTexture->asRenderTarget()); michael@0: SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); michael@0: convolve_gaussian(context, srcRect, dstRect, srcTexture, michael@0: Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect); michael@0: srcTexture = dstTexture; michael@0: srcRect = dstRect; michael@0: SkTSwap(dstTexture, tempTexture); michael@0: } michael@0: michael@0: if (sigmaY > 0.0f) { michael@0: if (scaleFactorY > 1 || sigmaX > 0.0f) { michael@0: // Clear out a radius below the srcRect to prevent the Y michael@0: // convolution from reading garbage. michael@0: clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, michael@0: srcIRect.width(), radiusY); michael@0: context->clear(&clearRect, 0x0, false); michael@0: } michael@0: michael@0: context->setRenderTarget(dstTexture->asRenderTarget()); michael@0: SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); michael@0: convolve_gaussian(context, srcRect, dstRect, srcTexture, michael@0: Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect); michael@0: srcTexture = dstTexture; michael@0: srcRect = dstRect; michael@0: SkTSwap(dstTexture, tempTexture); michael@0: } michael@0: michael@0: if (scaleFactorX > 1 || scaleFactorY > 1) { michael@0: // Clear one pixel to the right and below, to accommodate bilinear michael@0: // upsampling. michael@0: clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, michael@0: srcIRect.width() + 1, 1); michael@0: context->clear(&clearRect, 0x0, false); michael@0: clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, michael@0: 1, srcIRect.height()); michael@0: context->clear(&clearRect, 0x0, false); michael@0: SkMatrix matrix; michael@0: matrix.setIDiv(srcTexture->width(), srcTexture->height()); michael@0: context->setRenderTarget(dstTexture->asRenderTarget()); michael@0: michael@0: GrPaint paint; michael@0: // FIXME: this should be mitchell, not bilinear. michael@0: GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); michael@0: paint.addColorTextureEffect(srcTexture, matrix, params); michael@0: michael@0: SkRect dstRect(srcRect); michael@0: scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY); michael@0: context->drawRectToRect(paint, dstRect, srcRect); michael@0: srcRect = dstRect; michael@0: srcTexture = dstTexture; michael@0: SkTSwap(dstTexture, tempTexture); michael@0: } michael@0: if (srcTexture == temp1.texture()) { michael@0: return temp1.detach(); michael@0: } else if (srcTexture == temp2.texture()) { michael@0: return temp2.detach(); michael@0: } else { michael@0: srcTexture->ref(); michael@0: return srcTexture; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: }