2 * Copyright 2011 The Android Open Source Project
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
9 #include "SkBlurImageFilter.h"
10 #include "SkColorPriv.h"
11 #include "SkReadBuffer.h"
12 #include "SkWriteBuffer.h"
13 #include "SkGpuBlurUtils.h"
14 #include "SkBlurImage_opts.h"
16 #include "GrContext.h"
19 // This rather arbitrary-looking value results in a maximum box blur kernel size
20 // of 1000 pixels on the raster path, which matches the WebKit and Firefox
21 // implementations. Since the GPU path does not compute a box blur, putting
22 // the limit on sigma ensures consistent behaviour between the GPU and
24 #define MAX_SIGMA SkIntToScalar(532)
26 static SkVector mapSigma(const SkSize& localSigma, const SkMatrix& ctm) {
27 SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height());
28 ctm.mapVectors(&sigma, 1);
29 sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA);
30 sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA);
34 #ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
35 SkBlurImageFilter::SkBlurImageFilter(SkReadBuffer& buffer)
36 : INHERITED(1, buffer) {
37 fSigma.fWidth = buffer.readScalar();
38 fSigma.fHeight = buffer.readScalar();
39 buffer.validate(SkScalarIsFinite(fSigma.fWidth) &&
40 SkScalarIsFinite(fSigma.fHeight) &&
41 (fSigma.fWidth >= 0) &&
42 (fSigma.fHeight >= 0));
46 SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX,
49 const CropRect* cropRect,
51 : INHERITED(1, &input, cropRect, uniqueID), fSigma(SkSize::Make(sigmaX, sigmaY)) {
54 SkFlattenable* SkBlurImageFilter::CreateProc(SkReadBuffer& buffer) {
55 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
56 SkScalar sigmaX = buffer.readScalar();
57 SkScalar sigmaY = buffer.readScalar();
58 return Create(sigmaX, sigmaY, common.getInput(0), &common.cropRect(), common.uniqueID());
61 void SkBlurImageFilter::flatten(SkWriteBuffer& buffer) const {
62 this->INHERITED::flatten(buffer);
63 buffer.writeScalar(fSigma.fWidth);
64 buffer.writeScalar(fSigma.fHeight);
73 * In order to make memory accesses cache-friendly, we reorder the passes to
74 * use contiguous memory reads wherever possible.
76 * For example, the 6 passes of the X-and-Y blur case are rewritten as
77 * follows. Instead of 3 passes in X and 3 passes in Y, we perform
78 * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X,
79 * then 1 pass in X transposed to Y on write.
81 * +----+ +----+ +----+ +---+ +---+ +---+ +----+
82 * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB |
83 * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+
86 * In this way, two of the y-blurs become x-blurs applied to transposed
87 * images, and all memory reads are contiguous.
90 template<BlurDirection srcDirection, BlurDirection dstDirection>
91 static void boxBlur(const SkPMColor* src, int srcStride, SkPMColor* dst, int kernelSize,
92 int leftOffset, int rightOffset, int width, int height)
94 int rightBorder = SkMin32(rightOffset + 1, width);
95 int srcStrideX = srcDirection == kX ? 1 : srcStride;
96 int dstStrideX = dstDirection == kX ? 1 : height;
97 int srcStrideY = srcDirection == kX ? srcStride : 1;
98 int dstStrideY = dstDirection == kX ? width : 1;
99 uint32_t scale = (1 << 24) / kernelSize;
100 uint32_t half = 1 << 23;
101 for (int y = 0; y < height; ++y) {
102 int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
103 const SkPMColor* p = src;
104 for (int i = 0; i < rightBorder; ++i) {
105 sumA += SkGetPackedA32(*p);
106 sumR += SkGetPackedR32(*p);
107 sumG += SkGetPackedG32(*p);
108 sumB += SkGetPackedB32(*p);
112 const SkPMColor* sptr = src;
114 for (int x = 0; x < width; ++x) {
115 *dptr = SkPackARGB32((sumA * scale + half) >> 24,
116 (sumR * scale + half) >> 24,
117 (sumG * scale + half) >> 24,
118 (sumB * scale + half) >> 24);
119 if (x >= leftOffset) {
120 SkColor l = *(sptr - leftOffset * srcStrideX);
121 sumA -= SkGetPackedA32(l);
122 sumR -= SkGetPackedR32(l);
123 sumG -= SkGetPackedG32(l);
124 sumB -= SkGetPackedB32(l);
126 if (x + rightOffset + 1 < width) {
127 SkColor r = *(sptr + (rightOffset + 1) * srcStrideX);
128 sumA += SkGetPackedA32(r);
129 sumR += SkGetPackedR32(r);
130 sumG += SkGetPackedG32(r);
131 sumB += SkGetPackedB32(r);
134 if (srcDirection == kY) {
135 SK_PREFETCH(sptr + (rightOffset + 1) * srcStrideX);
144 static void getBox3Params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
147 float pi = SkScalarToFloat(SK_ScalarPI);
148 int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
151 *lowOffset = *highOffset = (d - 1) / 2;
155 *lowOffset = *highOffset - 1;
156 *kernelSize3 = d + 1;
160 bool SkBlurImageFilter::onFilterImage(Proxy* proxy,
161 const SkBitmap& source, const Context& ctx,
162 SkBitmap* dst, SkIPoint* offset) const {
163 SkBitmap src = source;
164 SkIPoint srcOffset = SkIPoint::Make(0, 0);
165 if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctx, &src, &srcOffset)) {
169 if (src.colorType() != kN32_SkColorType) {
173 SkIRect srcBounds, dstBounds;
174 if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &srcBounds, &src)) {
178 SkAutoLockPixels alp(src);
179 if (!src.getPixels()) {
183 if (!dst->tryAllocPixels(src.info().makeWH(srcBounds.width(), srcBounds.height()))) {
186 dst->getBounds(&dstBounds);
188 SkVector sigma = mapSigma(fSigma, ctx.ctm());
190 int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
191 int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
192 getBox3Params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
193 getBox3Params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
195 if (kernelSizeX < 0 || kernelSizeY < 0) {
199 if (kernelSizeX == 0 && kernelSizeY == 0) {
200 src.copyTo(dst, dst->colorType());
201 offset->fX = srcBounds.fLeft;
202 offset->fY = srcBounds.fTop;
207 if (!temp.tryAllocPixels(dst->info())) {
211 offset->fX = srcBounds.fLeft;
212 offset->fY = srcBounds.fTop;
213 srcBounds.offset(-srcOffset);
214 const SkPMColor* s = src.getAddr32(srcBounds.left(), srcBounds.top());
215 SkPMColor* t = temp.getAddr32(0, 0);
216 SkPMColor* d = dst->getAddr32(0, 0);
217 int w = dstBounds.width(), h = dstBounds.height();
218 int sw = src.rowBytesAsPixels();
219 SkBoxBlurProc boxBlurX, boxBlurY, boxBlurXY, boxBlurYX;
220 if (!SkBoxBlurGetPlatformProcs(&boxBlurX, &boxBlurY, &boxBlurXY, &boxBlurYX)) {
221 boxBlurX = boxBlur<kX, kX>;
222 boxBlurY = boxBlur<kY, kY>;
223 boxBlurXY = boxBlur<kX, kY>;
224 boxBlurYX = boxBlur<kY, kX>;
227 if (kernelSizeX > 0 && kernelSizeY > 0) {
228 boxBlurX(s, sw, t, kernelSizeX, lowOffsetX, highOffsetX, w, h);
229 boxBlurX(t, w, d, kernelSizeX, highOffsetX, lowOffsetX, w, h);
230 boxBlurXY(d, w, t, kernelSizeX3, highOffsetX, highOffsetX, w, h);
231 boxBlurX(t, h, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
232 boxBlurX(d, h, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
233 boxBlurXY(t, h, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
234 } else if (kernelSizeX > 0) {
235 boxBlurX(s, sw, d, kernelSizeX, lowOffsetX, highOffsetX, w, h);
236 boxBlurX(d, w, t, kernelSizeX, highOffsetX, lowOffsetX, w, h);
237 boxBlurX(t, w, d, kernelSizeX3, highOffsetX, highOffsetX, w, h);
238 } else if (kernelSizeY > 0) {
239 boxBlurYX(s, sw, d, kernelSizeY, lowOffsetY, highOffsetY, h, w);
240 boxBlurX(d, h, t, kernelSizeY, highOffsetY, lowOffsetY, h, w);
241 boxBlurXY(t, h, d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
247 void SkBlurImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
249 getInput(0)->computeFastBounds(src, dst);
254 dst->outset(SkScalarMul(fSigma.width(), SkIntToScalar(3)),
255 SkScalarMul(fSigma.height(), SkIntToScalar(3)));
258 bool SkBlurImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
259 SkIRect* dst) const {
260 SkIRect bounds = src;
261 SkVector sigma = mapSigma(fSigma, ctm);
262 bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
263 SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
264 if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
271 bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx,
272 SkBitmap* result, SkIPoint* offset) const {
274 SkBitmap input = src;
275 SkIPoint srcOffset = SkIPoint::Make(0, 0);
276 if (getInput(0) && !getInput(0)->getInputResultGPU(proxy, src, ctx, &input, &srcOffset)) {
280 if (!this->applyCropRect(ctx, proxy, input, &srcOffset, &rect, &input)) {
283 GrTexture* source = input.getTexture();
284 SkVector sigma = mapSigma(fSigma, ctx.ctm());
285 offset->fX = rect.fLeft;
286 offset->fY = rect.fTop;
287 rect.offset(-srcOffset);
288 SkAutoTUnref<GrTexture> tex(SkGpuBlurUtils::GaussianBlur(source->getContext(),
295 WrapTexture(tex, rect.width(), rect.height(), result);
298 SkDEBUGFAIL("Should not call in GPU-less build");