blob: 02fdf485d2c2399fa1d0d1e080384050bd03adca [file] [log] [blame]
robertphillips@google.com736dd032013-07-15 15:06:54 +00001/*
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"
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000014#include "effects/GrTextureDomainEffect.h"
robertphillips@google.com736dd032013-07-15 15:06:54 +000015#include "GrContext.h"
16#endif
17
18namespace SkGpuBlurUtils {
19
20#if SK_SUPPORT_GPU
21
22#define MAX_BLUR_SIGMA 4.0f
23
24static void scale_rect(SkRect* rect, float xScale, float yScale) {
commit-bot@chromium.org4b413c82013-11-25 19:44:07 +000025 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);
robertphillips@google.com736dd032013-07-15 15:06:54 +000029}
30
31static 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));
commit-bot@chromium.org96ae6882013-08-14 12:09:00 +000038 SkASSERT(*radius <= GrConvolutionEffect::kMaxKernelRadius);
robertphillips@google.com736dd032013-07-15 15:06:54 +000039 return sigma;
40}
41
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000042static 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]) {
robertphillips@google.com736dd032013-07-15 15:06:54 +000051 GrPaint paint;
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000052 paint.reset();
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000053 SkAutoTUnref<GrEffectRef> conv(GrConvolutionEffect::CreateGaussian(
54 texture, direction, radius, sigma, useBounds, bounds));
55 paint.reset();
robertphillips@google.com736dd032013-07-15 15:06:54 +000056 paint.addColorEffect(conv);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000057 context->drawRectToRect(paint, dstRect, srcRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +000058}
59
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000060static 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
robertphillips@google.com736dd032013-07-15 15:06:54 +0000115GrTexture* GaussianBlur(GrContext* context,
116 GrTexture* srcTexture,
117 bool canClobberSrc,
118 const SkRect& rect,
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000119 bool cropToRect,
skia.committer@gmail.com977409a2013-07-16 07:00:56 +0000120 float sigmaX,
robertphillips@google.com736dd032013-07-15 15:06:54 +0000121 float sigmaY) {
commit-bot@chromium.org96ae6882013-08-14 12:09:00 +0000122 SkASSERT(NULL != context);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000123
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
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000141 GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height()));
robertphillips@google.com736dd032013-07-15 15:06:54 +0000142
commit-bot@chromium.org96ae6882013-08-14 12:09:00 +0000143 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
robertphillips@google.com736dd032013-07-15 15:06:54 +0000144 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);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000166 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,
humper@google.comb86add12013-07-25 18:49:07 +0000177 GrTextureParams::kBilerp_FilterMode));
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000178 paint.addColorEffect(effect);
179 } else {
humper@google.comb86add12013-07-25 18:49:07 +0000180 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000181 paint.addColorTextureEffect(srcTexture, matrix, params);
182 }
robertphillips@google.com736dd032013-07-15 15:06:54 +0000183 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
184 i < scaleFactorY ? 0.5f : 1.0f);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000185 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());
robertphillips@google.com56ce48a2013-10-31 21:44:25 +0000200 context->clear(&clearRect, 0x0, false);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000201 }
202 context->setRenderTarget(dstTexture->asRenderTarget());
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000203 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +0000204 convolve_gaussian(context, srcRect, dstRect, srcTexture,
205 Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000206 srcTexture = dstTexture;
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000207 srcRect = dstRect;
robertphillips@google.com736dd032013-07-15 15:06:54 +0000208 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);
robertphillips@google.com56ce48a2013-10-31 21:44:25 +0000217 context->clear(&clearRect, 0x0, false);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000218 }
219
220 context->setRenderTarget(dstTexture->asRenderTarget());
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000221 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +0000222 convolve_gaussian(context, srcRect, dstRect, srcTexture,
223 Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000224 srcTexture = dstTexture;
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000225 srcRect = dstRect;
robertphillips@google.com736dd032013-07-15 15:06:54 +0000226 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);
robertphillips@google.com56ce48a2013-10-31 21:44:25 +0000234 context->clear(&clearRect, 0x0, false);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000235 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
236 1, srcIRect.height());
robertphillips@google.com56ce48a2013-10-31 21:44:25 +0000237 context->clear(&clearRect, 0x0, false);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000238 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.
humper@google.comb86add12013-07-25 18:49:07 +0000244 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000245 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
robertphillips@google.comcce41022013-07-15 15:47:10 +0000265}